From 3085a604300ed76d06a0011bd5555e419897b6cd Mon Sep 17 00:00:00 2001 From: Andrew Wang Date: Fri, 3 Mar 2017 13:00:22 -0800 Subject: [PATCH 001/188] HDFS-8112. Relax permission checking for EC related operations. --- .../server/namenode/FSDirErasureCodingOp.java | 74 ++++----- .../namenode/FSDirStatAndListingOp.java | 4 +- .../server/namenode/FSDirWriteFileOp.java | 12 +- .../hdfs/server/namenode/FSDirectory.java | 3 +- .../hdfs/server/namenode/FSEditLogLoader.java | 20 ++- .../hdfs/server/namenode/FSNamesystem.java | 11 +- .../hdfs/TestErasureCodingPolicies.java | 153 ++++++++++++++++-- 7 files changed, 203 insertions(+), 74 deletions(-) diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSDirErasureCodingOp.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSDirErasureCodingOp.java index 5240c7ed1b3..30a7b8bbe64 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSDirErasureCodingOp.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSDirErasureCodingOp.java @@ -40,6 +40,7 @@ import org.apache.hadoop.hdfs.protocol.HdfsFileStatus; import org.apache.hadoop.hdfs.server.namenode.FSDirectory.DirOp; import org.apache.hadoop.io.IOUtils; import org.apache.hadoop.io.WritableUtils; +import org.apache.hadoop.security.AccessControlException; import static org.apache.hadoop.hdfs.server.common.HdfsServerConstants.XATTR_ERASURECODING_POLICY; @@ -66,15 +67,15 @@ final class FSDirErasureCodingOp { * @return {@link HdfsFileStatus} * @throws IOException * @throws HadoopIllegalArgumentException if the policy is not enabled + * @throws AccessControlException if the user does not have write access */ static HdfsFileStatus setErasureCodingPolicy(final FSNamesystem fsn, final String srcArg, final String ecPolicyName, - final boolean logRetryCache) throws IOException { + final FSPermissionChecker pc, final boolean logRetryCache) + throws IOException, AccessControlException { assert fsn.hasWriteLock(); String src = srcArg; - FSPermissionChecker pc = null; - pc = fsn.getPermissionChecker(); FSDirectory fsd = fsn.getFSDirectory(); final INodesInPath iip; List xAttrs; @@ -88,6 +89,10 @@ final class FSDirErasureCodingOp { "policies."); } iip = fsd.resolvePath(pc, src, DirOp.WRITE_LINK); + // Write access is required to set erasure coding policy + if (fsd.isPermissionEnabled()) { + fsd.checkPathAccess(pc, iip, FsAction.WRITE); + } src = iip.getPath(); xAttrs = setErasureCodingPolicyXAttr(fsn, iip, ecPolicy); } finally { @@ -97,7 +102,7 @@ final class FSDirErasureCodingOp { return fsd.getAuditFileInfo(iip); } - static List setErasureCodingPolicyXAttr(final FSNamesystem fsn, + private static List setErasureCodingPolicyXAttr(final FSNamesystem fsn, final INodesInPath srcIIP, ErasureCodingPolicy ecPolicy) throws IOException { FSDirectory fsd = fsn.getFSDirectory(); assert fsd.hasWriteLock(); @@ -165,19 +170,24 @@ final class FSDirErasureCodingOp { * cache rebuilding * @return {@link HdfsFileStatus} * @throws IOException + * @throws AccessControlException if the user does not have write access */ static HdfsFileStatus unsetErasureCodingPolicy(final FSNamesystem fsn, - final String srcArg, final boolean logRetryCache) throws IOException { + final String srcArg, final FSPermissionChecker pc, + final boolean logRetryCache) throws IOException { assert fsn.hasWriteLock(); String src = srcArg; - FSPermissionChecker pc = fsn.getPermissionChecker(); FSDirectory fsd = fsn.getFSDirectory(); final INodesInPath iip; List xAttrs; fsd.writeLock(); try { iip = fsd.resolvePath(pc, src, DirOp.WRITE_LINK); + // Write access is required to unset erasure coding policy + if (fsd.isPermissionEnabled()) { + fsd.checkPathAccess(pc, iip, FsAction.WRITE); + } src = iip.getPath(); xAttrs = removeErasureCodingPolicyXAttr(fsn, iip); } finally { @@ -225,29 +235,23 @@ final class FSDirErasureCodingOp { * @return {@link ErasureCodingPolicy} * @throws IOException * @throws FileNotFoundException if the path does not exist. + * @throws AccessControlException if no read access */ static ErasureCodingPolicy getErasureCodingPolicy(final FSNamesystem fsn, - final String src) throws IOException { + final String src, FSPermissionChecker pc) + throws IOException, AccessControlException { assert fsn.hasReadLock(); - final INodesInPath iip = getINodesInPath(fsn, src); + FSDirectory fsd = fsn.getFSDirectory(); + final INodesInPath iip = fsd.resolvePath(pc, src, DirOp.READ); + if (fsn.isPermissionEnabled()) { + fsn.getFSDirectory().checkPathAccess(pc, iip, FsAction.READ); + } + if (iip.getLastINode() == null) { throw new FileNotFoundException("Path not found: " + iip.getPath()); } - return getErasureCodingPolicyForPath(fsn, iip); - } - - /** - * Check if the file or directory has an erasure coding policy. - * - * @param fsn namespace - * @param srcArg path - * @return Whether the file or directory has an erasure coding policy. - * @throws IOException - */ - static boolean hasErasureCodingPolicy(final FSNamesystem fsn, - final String srcArg) throws IOException { - return hasErasureCodingPolicy(fsn, getINodesInPath(fsn, srcArg)); + return getErasureCodingPolicyForPath(fsd, iip); } /** @@ -260,22 +264,22 @@ final class FSDirErasureCodingOp { */ static boolean hasErasureCodingPolicy(final FSNamesystem fsn, final INodesInPath iip) throws IOException { - return getErasureCodingPolicy(fsn, iip) != null; + return unprotectedGetErasureCodingPolicy(fsn, iip) != null; } /** - * Get the erasure coding policy. + * Get the erasure coding policy. This does not do any permission checking. * * @param fsn namespace * @param iip inodes in the path containing the file * @return {@link ErasureCodingPolicy} * @throws IOException */ - static ErasureCodingPolicy getErasureCodingPolicy(final FSNamesystem fsn, - final INodesInPath iip) throws IOException { + static ErasureCodingPolicy unprotectedGetErasureCodingPolicy( + final FSNamesystem fsn, final INodesInPath iip) throws IOException { assert fsn.hasReadLock(); - return getErasureCodingPolicyForPath(fsn, iip); + return getErasureCodingPolicyForPath(fsn.getFSDirectory(), iip); } /** @@ -290,21 +294,9 @@ final class FSDirErasureCodingOp { return fsn.getErasureCodingPolicyManager().getPolicies(); } - private static INodesInPath getINodesInPath(final FSNamesystem fsn, - final String srcArg) throws IOException { - final FSDirectory fsd = fsn.getFSDirectory(); - final FSPermissionChecker pc = fsn.getPermissionChecker(); - INodesInPath iip = fsd.resolvePath(pc, srcArg, DirOp.READ); - if (fsn.isPermissionEnabled()) { - fsn.getFSDirectory().checkPathAccess(pc, iip, FsAction.READ); - } - return iip; - } - - private static ErasureCodingPolicy getErasureCodingPolicyForPath(FSNamesystem fsn, - INodesInPath iip) throws IOException { + private static ErasureCodingPolicy getErasureCodingPolicyForPath( + FSDirectory fsd, INodesInPath iip) throws IOException { Preconditions.checkNotNull(iip, "INodes cannot be null"); - FSDirectory fsd = fsn.getFSDirectory(); fsd.readLock(); try { List inodes = iip.getReadOnlyINodes(); diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSDirStatAndListingOp.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSDirStatAndListingOp.java index ba7deece812..d0431ff1929 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSDirStatAndListingOp.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSDirStatAndListingOp.java @@ -172,7 +172,7 @@ class FSDirStatAndListingOp { final FileEncryptionInfo feInfo = FSDirEncryptionZoneOp.getFileEncryptionInfo(fsd, iip); final ErasureCodingPolicy ecPolicy = FSDirErasureCodingOp. - getErasureCodingPolicy(fsd.getFSNamesystem(), iip); + unprotectedGetErasureCodingPolicy(fsd.getFSNamesystem(), iip); final LocatedBlocks blocks = bm.createLocatedBlocks( inode.getBlocks(iip.getPathSnapshotId()), fileSize, isUc, offset, @@ -413,7 +413,7 @@ class FSDirStatAndListingOp { FileEncryptionInfo feInfo = null; final ErasureCodingPolicy ecPolicy = FSDirErasureCodingOp - .getErasureCodingPolicy(fsd.getFSNamesystem(), iip); + .unprotectedGetErasureCodingPolicy(fsd.getFSNamesystem(), iip); if (node.isFile()) { final INodeFile fileNode = node.asFile(); diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSDirWriteFileOp.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSDirWriteFileOp.java index 0a4d3b0cbe8..bb920049b33 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSDirWriteFileOp.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSDirWriteFileOp.java @@ -190,7 +190,8 @@ class FSDirWriteFileOp { blockType = pendingFile.getBlockType(); ErasureCodingPolicy ecPolicy = null; if (blockType == BlockType.STRIPED) { - ecPolicy = FSDirErasureCodingOp.getErasureCodingPolicy(fsn, src); + ecPolicy = + FSDirErasureCodingOp.unprotectedGetErasureCodingPolicy(fsn, iip); numTargets = (short) (ecPolicy.getSchema().getNumDataUnits() + ecPolicy.getSchema().getNumParityUnits()); } else { @@ -418,7 +419,7 @@ class FSDirWriteFileOp { // check if the file has an EC policy boolean isStriped = false; ErasureCodingPolicy ecPolicy = FSDirErasureCodingOp. - getErasureCodingPolicy(fsd.getFSNamesystem(), existing); + unprotectedGetErasureCodingPolicy(fsd.getFSNamesystem(), existing); if (ecPolicy != null) { isStriped = true; } @@ -475,8 +476,9 @@ class FSDirWriteFileOp { // associate new last block for the file final BlockInfo blockInfo; if (blockType == BlockType.STRIPED) { - ErasureCodingPolicy ecPolicy = FSDirErasureCodingOp.getErasureCodingPolicy( - fsd.getFSNamesystem(), inodesInPath); + ErasureCodingPolicy ecPolicy = + FSDirErasureCodingOp.unprotectedGetErasureCodingPolicy( + fsd.getFSNamesystem(), inodesInPath); short numDataUnits = (short) ecPolicy.getNumDataUnits(); short numParityUnits = (short) ecPolicy.getNumParityUnits(); short numLocations = (short) (numDataUnits + numParityUnits); @@ -529,7 +531,7 @@ class FSDirWriteFileOp { try { boolean isStriped = false; ErasureCodingPolicy ecPolicy = FSDirErasureCodingOp. - getErasureCodingPolicy(fsd.getFSNamesystem(), existing); + unprotectedGetErasureCodingPolicy(fsd.getFSNamesystem(), existing); if (ecPolicy != null) { isStriped = true; } diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSDirectory.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSDirectory.java index b21442df5e6..9ac61496ab7 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSDirectory.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSDirectory.java @@ -957,7 +957,8 @@ public class FSDirectory implements Closeable { final short replicationFactor; if (fileINode.isStriped()) { final ErasureCodingPolicy ecPolicy = - FSDirErasureCodingOp.getErasureCodingPolicy(namesystem, iip); + FSDirErasureCodingOp + .unprotectedGetErasureCodingPolicy(namesystem, iip); final short numDataUnits = (short) ecPolicy.getNumDataUnits(); final short numParityUnits = (short) ecPolicy.getNumParityUnits(); diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSEditLogLoader.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSEditLogLoader.java index e3ad0765ed7..ff36f1816f9 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSEditLogLoader.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSEditLogLoader.java @@ -415,8 +415,9 @@ public class FSEditLogLoader { // Update the salient file attributes. newFile.setAccessTime(addCloseOp.atime, Snapshot.CURRENT_STATE_ID); newFile.setModificationTime(addCloseOp.mtime, Snapshot.CURRENT_STATE_ID); - ErasureCodingPolicy ecPolicy = FSDirErasureCodingOp.getErasureCodingPolicy( - fsDir.getFSNamesystem(), iip); + ErasureCodingPolicy ecPolicy = + FSDirErasureCodingOp.unprotectedGetErasureCodingPolicy( + fsDir.getFSNamesystem(), iip); updateBlocks(fsDir, addCloseOp, iip, newFile, ecPolicy); break; } @@ -437,8 +438,9 @@ public class FSEditLogLoader { // Update the salient file attributes. file.setAccessTime(addCloseOp.atime, Snapshot.CURRENT_STATE_ID); file.setModificationTime(addCloseOp.mtime, Snapshot.CURRENT_STATE_ID); - ErasureCodingPolicy ecPolicy = FSDirErasureCodingOp.getErasureCodingPolicy( - fsDir.getFSNamesystem(), iip); + ErasureCodingPolicy ecPolicy = + FSDirErasureCodingOp.unprotectedGetErasureCodingPolicy( + fsDir.getFSNamesystem(), iip); updateBlocks(fsDir, addCloseOp, iip, file, ecPolicy); // Now close the file @@ -496,8 +498,9 @@ public class FSEditLogLoader { INodesInPath iip = fsDir.getINodesInPath(path, DirOp.READ); INodeFile oldFile = INodeFile.valueOf(iip.getLastINode(), path); // Update in-memory data structures - ErasureCodingPolicy ecPolicy = FSDirErasureCodingOp.getErasureCodingPolicy( - fsDir.getFSNamesystem(), iip); + ErasureCodingPolicy ecPolicy = + FSDirErasureCodingOp.unprotectedGetErasureCodingPolicy( + fsDir.getFSNamesystem(), iip); updateBlocks(fsDir, updateOp, iip, oldFile, ecPolicy); if (toAddRetryCache) { @@ -515,8 +518,9 @@ public class FSEditLogLoader { INodesInPath iip = fsDir.getINodesInPath(path, DirOp.READ); INodeFile oldFile = INodeFile.valueOf(iip.getLastINode(), path); // add the new block to the INodeFile - ErasureCodingPolicy ecPolicy = FSDirErasureCodingOp.getErasureCodingPolicy( - fsDir.getFSNamesystem(), iip); + ErasureCodingPolicy ecPolicy = + FSDirErasureCodingOp.unprotectedGetErasureCodingPolicy( + fsDir.getFSNamesystem(), iip); addNewBlock(addBlockOp, oldFile, ecPolicy); break; } 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 254ccfa492c..81c5759b573 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 @@ -6782,16 +6782,16 @@ public class FSNamesystem implements Namesystem, FSNamesystemMBean, final boolean logRetryCache) throws IOException, UnresolvedLinkException, SafeModeException, AccessControlException { final String operationName = "setErasureCodingPolicy"; - checkSuperuserPrivilege(); checkOperation(OperationCategory.WRITE); HdfsFileStatus resultingStat = null; + final FSPermissionChecker pc = getPermissionChecker(); boolean success = false; writeLock(); try { checkOperation(OperationCategory.WRITE); checkNameNodeSafeMode("Cannot set erasure coding policy on " + srcArg); resultingStat = FSDirErasureCodingOp.setErasureCodingPolicy(this, - srcArg, ecPolicyName, logRetryCache); + srcArg, ecPolicyName, pc, logRetryCache); success = true; } catch (AccessControlException ace) { logAuditEvent(success, operationName, srcArg, null, @@ -6818,16 +6818,16 @@ public class FSNamesystem implements Namesystem, FSNamesystemMBean, final boolean logRetryCache) throws IOException, UnresolvedLinkException, SafeModeException, AccessControlException { final String operationName = "unsetErasureCodingPolicy"; - checkSuperuserPrivilege(); checkOperation(OperationCategory.WRITE); HdfsFileStatus resultingStat = null; + final FSPermissionChecker pc = getPermissionChecker(); boolean success = false; writeLock(); try { checkOperation(OperationCategory.WRITE); checkNameNodeSafeMode("Cannot unset erasure coding policy on " + srcArg); resultingStat = FSDirErasureCodingOp.unsetErasureCodingPolicy(this, - srcArg, logRetryCache); + srcArg, pc, logRetryCache); success = true; } catch (AccessControlException ace) { logAuditEvent(success, operationName, srcArg, null, @@ -6849,10 +6849,11 @@ public class FSNamesystem implements Namesystem, FSNamesystemMBean, ErasureCodingPolicy getErasureCodingPolicy(String src) throws AccessControlException, UnresolvedLinkException, IOException { checkOperation(OperationCategory.READ); + FSPermissionChecker pc = getPermissionChecker(); readLock(); try { checkOperation(OperationCategory.READ); - return FSDirErasureCodingOp.getErasureCodingPolicy(this, src); + return FSDirErasureCodingOp.getErasureCodingPolicy(this, src, pc); } finally { readUnlock("getErasureCodingPolicy"); } diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestErasureCodingPolicies.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestErasureCodingPolicies.java index 8608626e59f..e6a822775d1 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestErasureCodingPolicies.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestErasureCodingPolicies.java @@ -19,6 +19,7 @@ package org.apache.hadoop.hdfs; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.CommonConfigurationKeys; +import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.fs.permission.FsPermission; import org.apache.hadoop.hdfs.protocol.DirectoryListing; @@ -30,12 +31,17 @@ import org.apache.hadoop.hdfs.client.HdfsAdmin; import org.apache.hadoop.hdfs.protocol.ErasureCodingPolicy; import org.apache.hadoop.hdfs.server.namenode.INodeFile; import org.apache.hadoop.io.erasurecode.ECSchema; +import org.apache.hadoop.security.AccessControlException; +import org.apache.hadoop.security.UserGroupInformation; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.Timeout; import java.io.FileNotFoundException; import java.io.IOException; +import java.security.PrivilegedExceptionAction; import java.util.Arrays; import java.util.Collection; @@ -51,6 +57,9 @@ public class TestErasureCodingPolicies { ErasureCodingPolicyManager.getSystemDefaultPolicy(); private FSNamesystem namesystem; + @Rule + public Timeout timeout = new Timeout(60 * 1000); + @Before public void setupCluster() throws IOException { conf = new HdfsConfiguration(); @@ -74,7 +83,7 @@ public class TestErasureCodingPolicies { * for pre-existing files (with replicated blocks) in an EC dir, getListing * should report them as non-ec. */ - @Test(timeout=60000) + @Test public void testReplicatedFileUnderECDir() throws IOException { final Path dir = new Path("/ec"); final Path replicatedFile = new Path(dir, "replicatedFile"); @@ -128,7 +137,7 @@ public class TestErasureCodingPolicies { assertNotNull(files[1].getErasureCodingPolicy()); } - @Test(timeout = 60000) + @Test public void testBasicSetECPolicy() throws IOException, InterruptedException { final Path testDir = new Path("/ec"); @@ -182,7 +191,7 @@ public class TestErasureCodingPolicies { } } - @Test(timeout = 60000) + @Test public void testMoveValidity() throws IOException, InterruptedException { final Path srcECDir = new Path("/srcEC"); final Path dstECDir = new Path("/dstEC"); @@ -219,7 +228,7 @@ public class TestErasureCodingPolicies { fs.rename(nonECFile, dstECDir); } - @Test(timeout = 60000) + @Test public void testReplication() throws IOException { final Path testDir = new Path("/ec"); fs.mkdir(testDir, FsPermission.getDirDefault()); @@ -237,7 +246,7 @@ public class TestErasureCodingPolicies { assertEquals(policy, fs.getErasureCodingPolicy(fooFile)); } - @Test(timeout = 60000) + @Test public void testGetErasureCodingPolicyWithSystemDefaultECPolicy() throws Exception { String src = "/ec"; final Path ecDir = new Path(src); @@ -254,7 +263,7 @@ public class TestErasureCodingPolicies { verifyErasureCodingInfo(src + "/child1", sysDefaultECPolicy); } - @Test(timeout = 60000) + @Test public void testGetErasureCodingPolicy() throws Exception { ErasureCodingPolicy[] sysECPolicies = ErasureCodingPolicyManager.getSystemPolicies(); @@ -284,13 +293,13 @@ public class TestErasureCodingPolicies { usingECPolicy, ecPolicy); } - @Test(timeout = 60000) - public void testCreationErasureCodingZoneWithInvalidPolicy() + @Test + public void testSetInvalidPolicy() throws IOException { ECSchema rsSchema = new ECSchema("rs", 4, 2); String policyName = "RS-4-2-128k"; int cellSize = 128 * 1024; - ErasureCodingPolicy ecPolicy= + ErasureCodingPolicy ecPolicy = new ErasureCodingPolicy(policyName, rsSchema, cellSize, (byte) -1); String src = "/ecDir4-2"; final Path ecDir = new Path(src); @@ -305,7 +314,7 @@ public class TestErasureCodingPolicies { } } - @Test(timeout = 60000) + @Test public void testGetAllErasureCodingPolicies() throws Exception { ErasureCodingPolicy[] sysECPolicies = ErasureCodingPolicyManager .getSystemPolicies(); @@ -315,7 +324,7 @@ public class TestErasureCodingPolicies { allECPolicies.containsAll(Arrays.asList(sysECPolicies))); } - @Test(timeout = 60000) + @Test public void testGetErasureCodingPolicyOnANonExistentFile() throws Exception { Path path = new Path("/ecDir"); try { @@ -335,7 +344,7 @@ public class TestErasureCodingPolicies { } } - @Test(timeout = 60000) + @Test public void testMultiplePoliciesCoExist() throws Exception { ErasureCodingPolicy[] sysPolicies = ErasureCodingPolicyManager.getSystemPolicies(); @@ -355,4 +364,124 @@ public class TestErasureCodingPolicies { } } } + + @Test + public void testPermissions() throws Exception { + UserGroupInformation user = + UserGroupInformation.createUserForTesting("ecuser", + new String[]{"ecgroup"}); + FileSystem userfs = user.doAs(new PrivilegedExceptionAction() { + @Override + public FileSystem run() throws Exception { + return FileSystem.get(conf); + } + }); + HdfsAdmin useradmin = user.doAs(new PrivilegedExceptionAction() { + @Override + public HdfsAdmin run() throws Exception { + return new HdfsAdmin(userfs.getUri(), conf); + } + }); + + // Create dir and set an EC policy, create an EC file + Path ecdir = new Path("/ecdir"); + Path ecfile = new Path(ecdir, "ecfile"); + fs.setPermission(new Path("/"), new FsPermission((short)0777)); + userfs.mkdirs(ecdir); + final String ecPolicyName = + ErasureCodingPolicyManager.getSystemPolicies()[0].getName(); + useradmin.setErasureCodingPolicy(ecdir, ecPolicyName); + assertEquals("Policy not present on dir", + ecPolicyName, + useradmin.getErasureCodingPolicy(ecdir).getName()); + userfs.create(ecfile).close(); + assertEquals("Policy not present on file", + ecPolicyName, + useradmin.getErasureCodingPolicy(ecfile).getName()); + + // Unset and re-set + useradmin.unsetErasureCodingPolicy(ecdir); + useradmin.setErasureCodingPolicy(ecdir, ecPolicyName); + + // Change write permissions and make sure set and unset are denied + userfs.setPermission(ecdir, new FsPermission((short)0555)); + try { + useradmin.setErasureCodingPolicy(ecdir, ecPolicyName); + fail("Should not be able to setECPolicy without write permissions"); + } catch (AccessControlException e) { + // pass + } + try { + useradmin.unsetErasureCodingPolicy(ecdir); + fail("Should not be able to unsetECPolicy without write permissions"); + } catch (AccessControlException e) { + // pass + } + + // Change the permissions again, check that set and unset work + userfs.setPermission(ecdir, new FsPermission((short)0640)); + useradmin.unsetErasureCodingPolicy(ecdir); + useradmin.setErasureCodingPolicy(ecdir, ecPolicyName); + + // Set, unset, and get with another user should be unauthorized + UserGroupInformation nobody = + UserGroupInformation.createUserForTesting("nobody", + new String[]{"nogroup"}); + HdfsAdmin noadmin = nobody.doAs(new PrivilegedExceptionAction() { + @Override + public HdfsAdmin run() throws Exception { + return new HdfsAdmin(userfs.getUri(), conf); + } + }); + try { + noadmin.setErasureCodingPolicy(ecdir, ecPolicyName); + fail("Should not be able to setECPolicy without write permissions"); + } catch (AccessControlException e) { + // pass + } + try { + noadmin.unsetErasureCodingPolicy(ecdir); + fail("Should not be able to unsetECPolicy without write permissions"); + } catch (AccessControlException e) { + // pass + } + try { + noadmin.getErasureCodingPolicy(ecdir); + fail("Should not be able to getECPolicy without write permissions"); + } catch (AccessControlException e) { + // pass + } + + // superuser can do whatever it wants + userfs.setPermission(ecdir, new FsPermission((short)0000)); + HdfsAdmin superadmin = new HdfsAdmin(fs.getUri(), conf); + superadmin.unsetErasureCodingPolicy(ecdir); + superadmin.setErasureCodingPolicy(ecdir, ecPolicyName); + superadmin.getErasureCodingPolicy(ecdir); + + // Normal user no longer has access + try { + useradmin.getErasureCodingPolicy(ecdir); + fail("Normal user should not have access"); + } catch (AccessControlException e) { + // pass + } + try { + useradmin.setErasureCodingPolicy(ecfile, ecPolicyName); + fail("Normal user should not have access"); + } catch (AccessControlException e) { + // pass + } + try { + useradmin.unsetErasureCodingPolicy(ecfile); + fail("Normal user should not have access"); + } catch (AccessControlException e) { + // pass + } + + // Everyone has access to getting the list of EC policies + useradmin.getErasureCodingPolicies(); + noadmin.getErasureCodingPolicies(); + superadmin.getErasureCodingPolicies(); + } } From 2148b83993fd8ce73bcbc7677c57ee5028a59cd4 Mon Sep 17 00:00:00 2001 From: Ray Chiang Date: Fri, 3 Mar 2017 12:55:45 -0800 Subject: [PATCH 002/188] YARN-6218. Fix TestAMRMClient when using FairScheduler. (Miklos Szegedi via rchiang) --- .../yarn/client/api/impl/TestAMRMClient.java | 139 +++++++++++------- .../scheduler/fair/FairScheduler.java | 3 +- 2 files changed, 87 insertions(+), 55 deletions(-) diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-client/src/test/java/org/apache/hadoop/yarn/client/api/impl/TestAMRMClient.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-client/src/test/java/org/apache/hadoop/yarn/client/api/impl/TestAMRMClient.java index 4f73bac5a47..43c02710f8c 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-client/src/test/java/org/apache/hadoop/yarn/client/api/impl/TestAMRMClient.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-client/src/test/java/org/apache/hadoop/yarn/client/api/impl/TestAMRMClient.java @@ -64,47 +64,71 @@ import org.apache.hadoop.yarn.exceptions.YarnException; import org.apache.hadoop.yarn.ipc.YarnRPC; import org.apache.hadoop.yarn.security.AMRMTokenIdentifier; import org.apache.hadoop.yarn.server.MiniYARNCluster; +import org.apache.hadoop.yarn.server.resourcemanager.RMContext; import org.apache.hadoop.yarn.server.resourcemanager.rmapp.attempt.RMAppAttempt; import org.apache.hadoop.yarn.server.resourcemanager.rmapp.attempt.RMAppAttemptState; +import org.apache.hadoop.yarn.server.resourcemanager.rmnode.RMNode; +import org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CapacityScheduler; +import org.apache.hadoop.yarn.server.resourcemanager.scheduler.event.NodeUpdateSchedulerEvent; +import org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FairScheduler; import org.apache.hadoop.yarn.server.resourcemanager.security.AMRMTokenSecretManager; import org.apache.hadoop.yarn.server.utils.BuilderUtils; import org.apache.hadoop.yarn.util.Records; import org.junit.After; -import org.junit.AfterClass; import org.junit.Assert; +import org.junit.Assume; import org.junit.Before; -import org.junit.BeforeClass; import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.eclipse.jetty.util.log.Log; import com.google.common.base.Supplier; +/** + * Test application master client class to resource manager. + */ +@RunWith(value = Parameterized.class) public class TestAMRMClient { - static Configuration conf = null; - static MiniYARNCluster yarnCluster = null; - static YarnClient yarnClient = null; - static List nodeReports = null; - static ApplicationAttemptId attemptId = null; - static int nodeCount = 3; + private String schedulerName = null; + private Configuration conf = null; + private MiniYARNCluster yarnCluster = null; + private YarnClient yarnClient = null; + private List nodeReports = null; + private ApplicationAttemptId attemptId = null; + private int nodeCount = 3; static final int rolling_interval_sec = 13; static final long am_expire_ms = 4000; - static Resource capability; - static Priority priority; - static Priority priority2; - static String node; - static String rack; - static String[] nodes; - static String[] racks; + private Resource capability; + private Priority priority; + private Priority priority2; + private String node; + private String rack; + private String[] nodes; + private String[] racks; private final static int DEFAULT_ITERATION = 3; - @BeforeClass - public static void setup() throws Exception { + public TestAMRMClient(String schedulerName) { + this.schedulerName = schedulerName; + } + + @Parameterized.Parameters + public static Collection data() { + List list = new ArrayList(2); + list.add(new Object[] {CapacityScheduler.class.getName()}); + list.add(new Object[] {FairScheduler.class.getName()}); + return list; + } + + @Before + public void setup() throws Exception { // start minicluster conf = new YarnConfiguration(); + conf.set(YarnConfiguration.RM_SCHEDULER, schedulerName); conf.setLong( YarnConfiguration.RM_AMRM_TOKEN_MASTER_KEY_ROLLING_INTERVAL_SECS, rolling_interval_sec); @@ -138,10 +162,7 @@ public class TestAMRMClient { rack = nodeReports.get(0).getRackName(); nodes = new String[]{ node }; racks = new String[]{ rack }; - } - - @Before - public void startApp() throws Exception { + // submit new app ApplicationSubmissionContext appContext = yarnClient.createApplication().getApplicationSubmissionContext(); @@ -199,13 +220,10 @@ public class TestAMRMClient { } @After - public void cancelApp() throws YarnException, IOException { + public void teardown() throws YarnException, IOException { yarnClient.killApplication(attemptId.getApplicationId()); attemptId = null; - } - - @AfterClass - public static void tearDown() { + if (yarnClient != null && yarnClient.getServiceState() == STATE.STARTED) { yarnClient.stop(); } @@ -656,8 +674,8 @@ public class TestAMRMClient { amClient.releaseAssignedContainer(container.getId()); } if(allocatedContainerCount < containersRequestedAny) { - // sleep to let NM's heartbeat to RM and trigger allocations - sleep(100); + // let NM heartbeat to RM and trigger allocations + triggerSchedulingWithNMHeartBeat(); } } @@ -678,7 +696,27 @@ public class TestAMRMClient { } } } - + + /** + * Make sure we get allocations regardless of timing issues. + */ + private void triggerSchedulingWithNMHeartBeat() { + // Simulate fair scheduler update thread + RMContext context = yarnCluster.getResourceManager().getRMContext(); + if (context.getScheduler() instanceof FairScheduler) { + FairScheduler scheduler = (FairScheduler)context.getScheduler(); + scheduler.update(); + } + // Trigger NM's heartbeat to RM and trigger allocations + for (RMNode rmNode : context.getRMNodes().values()) { + context.getScheduler().handle(new NodeUpdateSchedulerEvent(rmNode)); + } + if (context.getScheduler() instanceof FairScheduler) { + FairScheduler scheduler = (FairScheduler)context.getScheduler(); + scheduler.update(); + } + } + @Test (timeout=60000) public void testAllocationWithBlacklist() throws YarnException, IOException { AMRMClientImpl amClient = null; @@ -811,8 +849,8 @@ public class TestAMRMClient { allocatedContainerCount += allocResponse.getAllocatedContainers().size(); if(allocatedContainerCount == 0) { - // sleep to let NM's heartbeat to RM and trigger allocations - sleep(100); + // let NM heartbeat to RM and trigger allocations + triggerSchedulingWithNMHeartBeat(); } } return allocatedContainerCount; @@ -934,6 +972,8 @@ public class TestAMRMClient { @Test(timeout=60000) public void testAMRMClientWithContainerResourceChange() throws YarnException, IOException { + // Fair scheduler does not support resource change + Assume.assumeTrue(schedulerName.equals(CapacityScheduler.class.getName())); AMRMClient amClient = null; try { // start am rm client @@ -981,8 +1021,8 @@ public class TestAMRMClient { } // send allocation requests amClient.allocate(0.1f); - // sleep to let NM's heartbeat to RM and trigger allocations - sleep(150); + // let NM heartbeat to RM and trigger allocations + triggerSchedulingWithNMHeartBeat(); // get allocations AllocateResponse allocResponse = amClient.allocate(0.1f); List containers = allocResponse.getAllocatedContainers(); @@ -1012,14 +1052,14 @@ public class TestAMRMClient { if (status.getState() == ContainerState.RUNNING) { break; } - sleep(100); + sleep(10); } } } catch (YarnException e) { throw new AssertionError("Exception is not expected: " + e); } - // sleep to let NM's heartbeat to RM to confirm container launch - sleep(200); + // let NM's heartbeat to RM to confirm container launch + triggerSchedulingWithNMHeartBeat(); return containers; } @@ -1079,7 +1119,7 @@ public class TestAMRMClient { allocResponse.getUpdatedContainers(); Assert.assertEquals(1, updatedContainers.size()); // we should get increase allocation after the next NM's heartbeat to RM - sleep(150); + triggerSchedulingWithNMHeartBeat(); // get allocations allocResponse = amClient.allocate(0.1f); updatedContainers = @@ -1142,8 +1182,8 @@ public class TestAMRMClient { } if(allocatedContainerCount < containersRequestedAny) { - // sleep to let NM's heartbeat to RM and trigger allocations - sleep(100); + // let NM heartbeat to RM and trigger allocations + triggerSchedulingWithNMHeartBeat(); } } @@ -1225,8 +1265,8 @@ public class TestAMRMClient { } } if(numIterations > 0) { - // sleep to make sure NM's heartbeat - sleep(100); + // let NM heartbeat to RM and trigger allocations + triggerSchedulingWithNMHeartBeat(); } } assertEquals(0, amClient.ask.size()); @@ -1284,8 +1324,8 @@ public class TestAMRMClient { } if(allocatedContainers.size() < containersRequestedAny) { - // sleep to let NM's heartbeat to RM and trigger allocations - sleep(100); + // let NM heartbeat to RM and trigger allocations + triggerSchedulingWithNMHeartBeat(); } } @@ -1396,12 +1436,7 @@ public class TestAMRMClient { while (System.currentTimeMillis() - startTime < rolling_interval_sec * 1000) { amClient.allocate(0.1f); - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } + sleep(1000); } amClient.allocate(0.1f); @@ -1461,11 +1496,7 @@ public class TestAMRMClient { } } amClient.allocate(0.1f); - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - // DO NOTHING - } + sleep(1000); } try { diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/fair/FairScheduler.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/fair/FairScheduler.java index 0599414085c..4f3e4f9140e 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/fair/FairScheduler.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/fair/FairScheduler.java @@ -349,7 +349,8 @@ public class FairScheduler extends * fair shares, deficits, minimum slot allocations, and amount of used and * required resources per job. */ - protected void update() { + @VisibleForTesting + public void update() { try { writeLock.lock(); From ac5ae0065a127ac150a887fa6c6f3cffd86ef733 Mon Sep 17 00:00:00 2001 From: Jing Zhao Date: Fri, 3 Mar 2017 13:31:20 -0800 Subject: [PATCH 003/188] HDFS-11476. Fix NPE in FsDatasetImpl#checkAndUpdate. Contributed by Xiaobing Zhou. --- .../datanode/fsdataset/impl/FsDatasetImpl.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/fsdataset/impl/FsDatasetImpl.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/fsdataset/impl/FsDatasetImpl.java index 6d00d75b53f..aff19ce97e0 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/fsdataset/impl/FsDatasetImpl.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/fsdataset/impl/FsDatasetImpl.java @@ -2282,12 +2282,14 @@ class FsDatasetImpl implements FsDatasetSpi { if (memBlockInfo.getGenerationStamp() != diskGS) { File memMetaFile = FsDatasetUtil.getMetaFile(diskFile, memBlockInfo.getGenerationStamp()); - if (memMetaFile.exists()) { - if (memMetaFile.compareTo(diskMetaFile) != 0) { - LOG.warn("Metadata file in memory " - + memMetaFile.getAbsolutePath() - + " does not match file found by scan " - + (diskMetaFile == null? null: diskMetaFile.getAbsolutePath())); + if (fileIoProvider.exists(vol, memMetaFile)) { + String warningPrefix = "Metadata file in memory " + + memMetaFile.getAbsolutePath() + + " does not match file found by scan "; + if (!diskMetaFileExists) { + LOG.warn(warningPrefix + "null"); + } else if (memMetaFile.compareTo(diskMetaFile) != 0) { + LOG.warn(warningPrefix + diskMetaFile.getAbsolutePath()); } } else { // Metadata file corresponding to block in memory is missing From 8db7a8c3aea3d989361f32cca5b271e9653773b6 Mon Sep 17 00:00:00 2001 From: Junping Du Date: Fri, 3 Mar 2017 14:39:31 -0800 Subject: [PATCH 004/188] YARN-6271. yarn rmadin -getGroups returns information from standby RM. Contributed by Jian He. --- .../hadoop/yarn/server/resourcemanager/AdminService.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/AdminService.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/AdminService.java index 9dda57e2d22..bcf7309f883 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/AdminService.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/AdminService.java @@ -572,6 +572,15 @@ public class AdminService extends CompositeService implements @Override public String[] getGroupsForUser(String user) throws IOException { + String operation = "getGroupsForUser"; + UserGroupInformation ugi; + try { + ugi = checkAcls(operation); + } catch (YarnException e) { + // The interface is from hadoop-common which does not accept YarnException + throw new IOException(e); + } + checkRMStatus(ugi.getShortUserName(), operation, "get groups for user"); return UserGroupInformation.createRemoteUser(user).getGroupNames(); } From 279d187f723d01658ef8698a29263652e2a05618 Mon Sep 17 00:00:00 2001 From: Jian He Date: Fri, 3 Mar 2017 15:23:33 -0800 Subject: [PATCH 005/188] YARN-6270. WebUtils.getRMWebAppURLWithScheme() needs to honor RM HA setting. Contributed by Xuan Gong --- .../hadoop/yarn/webapp/util/WebAppUtils.java | 43 +++++++++++++++---- .../yarn/conf/TestYarnConfiguration.java | 16 +++++++ 2 files changed, 50 insertions(+), 9 deletions(-) diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/util/WebAppUtils.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/util/WebAppUtils.java index e412173b40b..64a4b2ba38f 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/util/WebAppUtils.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/util/WebAppUtils.java @@ -89,21 +89,46 @@ public class WebAppUtils { hostName + ":" + port); } } - - public static String getRMWebAppURLWithScheme(Configuration conf) { - return getHttpSchemePrefix(conf) + getRMWebAppURLWithoutScheme(conf); - } - - public static String getRMWebAppURLWithoutScheme(Configuration conf) { - if (YarnConfiguration.useHttps(conf)) { - return conf.get(YarnConfiguration.RM_WEBAPP_HTTPS_ADDRESS, + + public static String getRMWebAppURLWithoutScheme(Configuration conf, + boolean isHAEnabled) { + YarnConfiguration yarnConfig = new YarnConfiguration(conf); + // set RM_ID if we have not configure it. + if (isHAEnabled) { + String rmId = yarnConfig.get(YarnConfiguration.RM_HA_ID); + if (rmId == null || rmId.isEmpty()) { + List rmIds = new ArrayList<>(HAUtil.getRMHAIds(conf)); + if (rmIds != null && !rmIds.isEmpty()) { + yarnConfig.set(YarnConfiguration.RM_HA_ID, rmIds.get(0)); + } + } + } + if (YarnConfiguration.useHttps(yarnConfig)) { + if (isHAEnabled) { + return HAUtil.getConfValueForRMInstance( + YarnConfiguration.RM_WEBAPP_HTTPS_ADDRESS, yarnConfig); + } + return yarnConfig.get(YarnConfiguration.RM_WEBAPP_HTTPS_ADDRESS, YarnConfiguration.DEFAULT_RM_WEBAPP_HTTPS_ADDRESS); }else { - return conf.get(YarnConfiguration.RM_WEBAPP_ADDRESS, + if (isHAEnabled) { + return HAUtil.getConfValueForRMInstance( + YarnConfiguration.RM_WEBAPP_ADDRESS, yarnConfig); + } + return yarnConfig.get(YarnConfiguration.RM_WEBAPP_ADDRESS, YarnConfiguration.DEFAULT_RM_WEBAPP_ADDRESS); } } + public static String getRMWebAppURLWithScheme(Configuration conf) { + return getHttpSchemePrefix(conf) + getRMWebAppURLWithoutScheme( + conf, HAUtil.isHAEnabled(conf)); + } + + public static String getRMWebAppURLWithoutScheme(Configuration conf) { + return getRMWebAppURLWithoutScheme(conf, false); + } + public static List getProxyHostsAndPortsForAmFilter( Configuration conf) { List addrs = new ArrayList(); diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/test/java/org/apache/hadoop/yarn/conf/TestYarnConfiguration.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/test/java/org/apache/hadoop/yarn/conf/TestYarnConfiguration.java index 2e76865ec7f..738942300a0 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/test/java/org/apache/hadoop/yarn/conf/TestYarnConfiguration.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/test/java/org/apache/hadoop/yarn/conf/TestYarnConfiguration.java @@ -41,6 +41,22 @@ public class TestYarnConfiguration { // specifically add slashes and Jetty doesn't handle double slashes. Assert.assertNotSame("RM Web Url is not correct", "http://0.0.0.0:8088", rmWebUrl); + + // test it in HA scenario + conf.setBoolean(YarnConfiguration.RM_HA_ENABLED, true); + conf.set(YarnConfiguration.RM_HA_IDS, "rm1, rm2"); + conf.set("yarn.resourcemanager.webapp.address.rm1", "10.10.10.10:18088"); + conf.set("yarn.resourcemanager.webapp.address.rm2", "20.20.20.20:28088"); + String rmWebUrlinHA = WebAppUtils.getRMWebAppURLWithScheme(conf); + Assert.assertEquals("http://10.10.10.10:18088", rmWebUrlinHA); + + YarnConfiguration conf2 = new YarnConfiguration(); + conf2.setBoolean(YarnConfiguration.RM_HA_ENABLED, true); + conf2.set(YarnConfiguration.RM_HA_IDS, "rm1, rm2"); + conf2.set("yarn.resourcemanager.hostname.rm1", "30.30.30.30"); + conf2.set("yarn.resourcemanager.hostname.rm2", "40.40.40.40"); + String rmWebUrlinHA2 = WebAppUtils.getRMWebAppURLWithScheme(conf2); + Assert.assertEquals("http://30.30.30.30:8088", rmWebUrlinHA2); } @Test From 6b7cd62b8cf12616b13142f2eb2cfc2f25796f0f Mon Sep 17 00:00:00 2001 From: Mingliang Liu Date: Fri, 3 Mar 2017 17:06:03 -0800 Subject: [PATCH 006/188] HADOOP-13930. Azure: Add Authorization support to WASB. Contributed by Sivaguru Sankaridurg and Dushyanth --- .../src/main/resources/core-default.xml | 10 + .../conf/TestCommonConfigurationFields.java | 2 + .../fs/azure/AzureNativeFileSystemStore.java | 4 +- .../fs/azure/NativeAzureFileSystem.java | 155 +++++++++- .../fs/azure/RemoteSASKeyGeneratorImpl.java | 185 ++++++++---- .../fs/azure/RemoteWasbAuthorizerImpl.java | 247 ++++++++++++++++ .../fs/azure/SecureStorageInterfaceImpl.java | 6 +- .../fs/azure/WasbAuthorizationException.java | 40 +++ .../fs/azure/WasbAuthorizationOperations.java | 44 +++ .../fs/azure/WasbAuthorizerInterface.java | 47 +++ .../hadoop/fs/azure/security/Constants.java | 54 ++++ .../WasbDelegationTokenIdentifier.java | 48 +++ .../fs/azure/security/WasbTokenRenewer.java | 124 ++++++++ .../hadoop/fs/azure/security/package.html | 28 ++ ...ache.hadoop.security.token.TokenIdentifier | 16 + ....apache.hadoop.security.token.TokenRenewer | 16 + .../hadoop-azure/src/site/markdown/index.md | 34 +++ .../fs/azure/MockWasbAuthorizerImpl.java | 102 +++++++ ...estNativeAzureFileSystemAuthorization.java | 277 ++++++++++++++++++ 19 files changed, 1374 insertions(+), 65 deletions(-) create mode 100644 hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/RemoteWasbAuthorizerImpl.java create mode 100644 hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/WasbAuthorizationException.java create mode 100644 hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/WasbAuthorizationOperations.java create mode 100644 hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/WasbAuthorizerInterface.java create mode 100644 hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/security/Constants.java create mode 100644 hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/security/WasbDelegationTokenIdentifier.java create mode 100644 hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/security/WasbTokenRenewer.java create mode 100644 hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/security/package.html create mode 100644 hadoop-tools/hadoop-azure/src/main/resources/META-INF/services/org.apache.hadoop.security.token.TokenIdentifier create mode 100644 hadoop-tools/hadoop-azure/src/main/resources/META-INF/services/org.apache.hadoop.security.token.TokenRenewer create mode 100644 hadoop-tools/hadoop-azure/src/test/java/org/apache/hadoop/fs/azure/MockWasbAuthorizerImpl.java create mode 100644 hadoop-tools/hadoop-azure/src/test/java/org/apache/hadoop/fs/azure/TestNativeAzureFileSystemAuthorization.java diff --git a/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml b/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml index 35be56bf81e..52b58eddb1e 100644 --- a/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml +++ b/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml @@ -1292,6 +1292,16 @@ to specify the time (such as 2s, 2m, 1h, etc.). + + fs.azure.authorization + false + + Config flag to enable authorization support in WASB. Setting it to "true" enables + authorization support to WASB. Currently WASB authorization requires a remote service + to provide authorization that needs to be specified via fs.azure.authorization.remote.service.url + configuration + + diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/conf/TestCommonConfigurationFields.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/conf/TestCommonConfigurationFields.java index 966a8ac0d08..7410d297a09 100644 --- a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/conf/TestCommonConfigurationFields.java +++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/conf/TestCommonConfigurationFields.java @@ -181,6 +181,8 @@ public class TestCommonConfigurationFields extends TestConfigurationFieldsBase { xmlPropsToSkipCompare.add("io.compression.codec.bzip2.library"); // - org.apache.hadoop.io.SequenceFile xmlPropsToSkipCompare.add("io.seqfile.local.dir"); + // - org.apache.hadoop.fs.azure.NativeAzureFileSystem + xmlPropsToSkipCompare.add("fs.azure.authorization"); } diff --git a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/AzureNativeFileSystemStore.java b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/AzureNativeFileSystemStore.java index 07c389ce0c2..9d7ac80cdfd 100644 --- a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/AzureNativeFileSystemStore.java +++ b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/AzureNativeFileSystemStore.java @@ -303,7 +303,7 @@ public class AzureNativeFileSystemStore implements NativeFileSystemStore { private boolean useSecureMode = false; private boolean useLocalSasKeyMode = false; - private String delegationToken; + /** * A test hook interface that can modify the operation context we use for * Azure Storage operations, e.g. to inject errors. @@ -478,7 +478,7 @@ public class AzureNativeFileSystemStore implements NativeFileSystemStore { this.storageInteractionLayer = new StorageInterfaceImpl(); } else { this.storageInteractionLayer = new SecureStorageInterfaceImpl( - useLocalSasKeyMode, conf, delegationToken); + useLocalSasKeyMode, conf); } } diff --git a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/NativeAzureFileSystem.java b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/NativeAzureFileSystem.java index b742e533712..0dfefaf196c 100644 --- a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/NativeAzureFileSystem.java +++ b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/NativeAzureFileSystem.java @@ -25,9 +25,12 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.net.InetAddress; import java.net.URI; import java.net.URISyntaxException; +import java.net.URL; import java.nio.charset.Charset; +import java.security.PrivilegedExceptionAction; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; @@ -61,10 +64,15 @@ import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.fs.azure.metrics.AzureFileSystemInstrumentation; import org.apache.hadoop.fs.azure.metrics.AzureFileSystemMetricsSystem; +import org.apache.hadoop.fs.azure.security.Constants; import org.apache.hadoop.fs.permission.FsPermission; import org.apache.hadoop.fs.permission.PermissionStatus; import org.apache.hadoop.io.IOUtils; import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.security.token.Token; +import org.apache.hadoop.security.token.delegation.web.DelegationTokenAuthenticatedURL; +import org.apache.hadoop.security.token.delegation.web.DelegationTokenAuthenticator; +import org.apache.hadoop.security.token.delegation.web.KerberosDelegationTokenAuthenticator; import org.apache.hadoop.util.Progressable; import org.apache.hadoop.util.Time; import org.slf4j.Logger; @@ -1096,7 +1104,39 @@ public class NativeAzureFileSystem extends FileSystem { // A counter to create unique (within-process) names for my metrics sources. private static AtomicInteger metricsSourceNameCounter = new AtomicInteger(); private boolean appendSupportEnabled = false; - + private DelegationTokenAuthenticatedURL authURL; + private DelegationTokenAuthenticatedURL.Token authToken = new DelegationTokenAuthenticatedURL.Token(); + private String credServiceUrl; + + /** + * Configuration key to enable authorization support in WASB. + */ + public static final String KEY_AZURE_AUTHORIZATION = + "fs.azure.authorization"; + + /** + * Default value for the authorization support in WASB. + */ + private static final boolean DEFAULT_AZURE_AUTHORIZATION = false; + + /** + * Flag controlling authorization support in WASB. + */ + private boolean azureAuthorization = false; + + /** + * Flag controlling Kerberos support in WASB. + */ + private boolean kerberosSupportEnabled = false; + + /** + * Authorizer to use when authorization support is enabled in + * WASB. + */ + private WasbAuthorizerInterface authorizer = null; + + private String delegationToken = null; + public NativeAzureFileSystem() { // set store in initialize() } @@ -1227,6 +1267,31 @@ public class NativeAzureFileSystem extends FileSystem { // Initialize thread counts from user configuration deleteThreadCount = conf.getInt(AZURE_DELETE_THREADS, DEFAULT_AZURE_DELETE_THREADS); renameThreadCount = conf.getInt(AZURE_RENAME_THREADS, DEFAULT_AZURE_RENAME_THREADS); + + this.azureAuthorization = conf.getBoolean(KEY_AZURE_AUTHORIZATION, + DEFAULT_AZURE_AUTHORIZATION); + this.kerberosSupportEnabled = conf.getBoolean( + Constants.AZURE_KERBEROS_SUPPORT_PROPERTY_NAME, false); + + if (this.azureAuthorization) { + + this.authorizer = + new RemoteWasbAuthorizerImpl(); + authorizer.init(conf); + } + if (UserGroupInformation.isSecurityEnabled() && kerberosSupportEnabled) { + DelegationTokenAuthenticator authenticator = new KerberosDelegationTokenAuthenticator(); + authURL = new DelegationTokenAuthenticatedURL(authenticator); + credServiceUrl = conf.get(Constants.KEY_CRED_SERVICE_URL, String + .format("http://%s:%s", + InetAddress.getLocalHost().getCanonicalHostName(), + Constants.DEFAULT_CRED_SERVICE_PORT)); + } + } + + @VisibleForTesting + public void updateWasbAuthorizer(WasbAuthorizerInterface authorizer) { + this.authorizer = authorizer; } private NativeFileSystemStore createDefaultStore(Configuration conf) { @@ -1340,6 +1405,15 @@ public class NativeAzureFileSystem extends FileSystem { return store; } + private void performAuthCheck(String path, String accessType, + String operation) throws WasbAuthorizationException, IOException { + + if (azureAuthorization && !this.authorizer.authorize(path, accessType)) { + throw new WasbAuthorizationException(operation + + " operation for Path : " + path + " not allowed"); + } + } + /** * Gets the metrics source for this file system. * This is mainly here for unit testing purposes. @@ -1362,6 +1436,10 @@ public class NativeAzureFileSystem extends FileSystem { LOG.debug("Opening file: {} for append", f); Path absolutePath = makeAbsolute(f); + + performAuthCheck(absolutePath.toString(), + WasbAuthorizationOperations.WRITE.toString(), "append"); + String key = pathToKey(absolutePath); FileMetadata meta = null; try { @@ -1562,6 +1640,10 @@ public class NativeAzureFileSystem extends FileSystem { } Path absolutePath = makeAbsolute(f); + + performAuthCheck(absolutePath.toString(), + WasbAuthorizationOperations.WRITE.toString(), "create"); + String key = pathToKey(absolutePath); FileMetadata existingMetadata = store.retrieveMetadata(key); @@ -1684,6 +1766,10 @@ public class NativeAzureFileSystem extends FileSystem { LOG.debug("Deleting file: {}", f.toString()); Path absolutePath = makeAbsolute(f); + + performAuthCheck(absolutePath.toString(), + WasbAuthorizationOperations.EXECUTE.toString(), "delete"); + String key = pathToKey(absolutePath); // Capture the metadata for the path. @@ -1954,6 +2040,10 @@ public class NativeAzureFileSystem extends FileSystem { // Capture the absolute path and the path to key. Path absolutePath = makeAbsolute(f); + + performAuthCheck(absolutePath.toString(), + WasbAuthorizationOperations.EXECUTE.toString(), "getFileStatus"); + String key = pathToKey(absolutePath); if (key.length() == 0) { // root always exists return newDirectory(null, absolutePath); @@ -2052,6 +2142,10 @@ public class NativeAzureFileSystem extends FileSystem { LOG.debug("Listing status for {}", f.toString()); Path absolutePath = makeAbsolute(f); + + performAuthCheck(absolutePath.toString(), + WasbAuthorizationOperations.EXECUTE.toString(), "list"); + String key = pathToKey(absolutePath); Set status = new TreeSet(); FileMetadata meta = null; @@ -2274,6 +2368,10 @@ public class NativeAzureFileSystem extends FileSystem { } Path absolutePath = makeAbsolute(f); + + performAuthCheck(absolutePath.toString(), + WasbAuthorizationOperations.EXECUTE.toString(), "mkdirs"); + PermissionStatus permissionStatus = null; if(noUmask) { // ensure owner still has wx permissions at the minimum @@ -2327,6 +2425,10 @@ public class NativeAzureFileSystem extends FileSystem { LOG.debug("Opening file: {}", f.toString()); Path absolutePath = makeAbsolute(f); + + performAuthCheck(absolutePath.toString(), + WasbAuthorizationOperations.READ.toString(), "read"); + String key = pathToKey(absolutePath); FileMetadata meta = null; try { @@ -2383,7 +2485,12 @@ public class NativeAzureFileSystem extends FileSystem { + " through WASB that has colons in the name"); } - String srcKey = pathToKey(makeAbsolute(src)); + Path absolutePath = makeAbsolute(src); + + performAuthCheck(absolutePath.toString(), + WasbAuthorizationOperations.EXECUTE.toString(), "rename"); + + String srcKey = pathToKey(absolutePath); if (srcKey.length() == 0) { // Cannot rename root of file system @@ -2685,6 +2792,10 @@ public class NativeAzureFileSystem extends FileSystem { @Override public void setPermission(Path p, FsPermission permission) throws FileNotFoundException, IOException { Path absolutePath = makeAbsolute(p); + + performAuthCheck(absolutePath.toString(), + WasbAuthorizationOperations.EXECUTE.toString(), "setPermission"); + String key = pathToKey(absolutePath); FileMetadata metadata = null; try { @@ -2723,6 +2834,10 @@ public class NativeAzureFileSystem extends FileSystem { public void setOwner(Path p, String username, String groupname) throws IOException { Path absolutePath = makeAbsolute(p); + + performAuthCheck(absolutePath.toString(), + WasbAuthorizationOperations.EXECUTE.toString(), "setOwner"); + String key = pathToKey(absolutePath); FileMetadata metadata = null; @@ -2785,6 +2900,42 @@ public class NativeAzureFileSystem extends FileSystem { isClosed = true; } + @Override + public Token getDelegationToken(final String renewer) throws IOException { + if(kerberosSupportEnabled) { + try { + final UserGroupInformation ugi = UserGroupInformation.getCurrentUser(); + UserGroupInformation connectUgi = ugi.getRealUser(); + final UserGroupInformation proxyUser = connectUgi; + if (connectUgi == null) { + connectUgi = ugi; + } + if(!connectUgi.hasKerberosCredentials()){ + connectUgi = UserGroupInformation.getLoginUser(); + } + connectUgi.checkTGTAndReloginFromKeytab(); + return connectUgi.doAs(new PrivilegedExceptionAction>() { + @Override + public Token run() throws Exception { + return authURL.getDelegationToken(new URL(credServiceUrl + + Constants.DEFAULT_DELEGATION_TOKEN_MANAGER_ENDPOINT), + authToken, renewer, (proxyUser != null)? ugi.getShortUserName(): null); + } + }); + } catch (Exception ex) { + LOG.error("Error in fetching the delegation token from remote service", + ex); + if (ex instanceof IOException) { + throw (IOException) ex; + } else { + throw new IOException(ex); + } + } + } else { + return super.getDelegationToken(renewer); + } + } + /** * A handler that defines what to do with blobs whose upload was * interrupted. diff --git a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/RemoteSASKeyGeneratorImpl.java b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/RemoteSASKeyGeneratorImpl.java index 404419d7422..43672b27a66 100644 --- a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/RemoteSASKeyGeneratorImpl.java +++ b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/RemoteSASKeyGeneratorImpl.java @@ -19,10 +19,23 @@ package org.apache.hadoop.fs.azure; import java.io.IOException; +import java.net.InetAddress; import java.net.URI; import java.net.URISyntaxException; +import java.net.UnknownHostException; +import java.security.PrivilegedExceptionAction; +import java.util.Iterator; +import org.apache.commons.lang.Validate; import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.azure.security.Constants; +import org.apache.hadoop.fs.azure.security.WasbDelegationTokenIdentifier; +import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.security.authentication.client.AuthenticatedURL; +import org.apache.hadoop.security.authentication.client.Authenticator; +import org.apache.hadoop.security.token.Token; +import org.apache.hadoop.security.token.TokenIdentifier; +import org.apache.hadoop.security.token.delegation.web.KerberosDelegationTokenAuthenticator; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.utils.URIBuilder; import org.slf4j.Logger; @@ -43,12 +56,6 @@ public class RemoteSASKeyGeneratorImpl extends SASKeyGeneratorImpl { public static final Logger LOG = LoggerFactory.getLogger(AzureNativeFileSystemStore.class); - /** - * Configuration parameter name expected in the Configuration - * object to provide the url of the remote service {@value} - */ - private static final String KEY_CRED_SERVICE_URL = - "fs.azure.cred.service.url"; /** * Container SAS Key generation OP name. {@value} @@ -82,7 +89,7 @@ public class RemoteSASKeyGeneratorImpl extends SASKeyGeneratorImpl { * Query parameter name for user info {@value} */ private static final String DELEGATION_TOKEN_QUERY_PARAM_NAME = - "delegation_token"; + "delegation"; /** * Query parameter name for the relative path inside the storage @@ -94,23 +101,39 @@ public class RemoteSASKeyGeneratorImpl extends SASKeyGeneratorImpl { private String delegationToken = ""; private String credServiceUrl = ""; private WasbRemoteCallHelper remoteCallHelper = null; + private boolean isSecurityEnabled; + private boolean isKerberosSupportEnabled; public RemoteSASKeyGeneratorImpl(Configuration conf) { super(conf); } - public boolean initialize(Configuration conf, String delegationToken) { + public boolean initialize(Configuration conf) { LOG.debug("Initializing RemoteSASKeyGeneratorImpl instance"); - credServiceUrl = conf.get(KEY_CRED_SERVICE_URL); - - if (delegationToken == null || delegationToken.isEmpty()) { - LOG.error("Delegation Token not provided for initialization" - + " of RemoteSASKeyGenerator"); - return false; + Iterator> tokenIterator = null; + try { + tokenIterator = UserGroupInformation.getCurrentUser().getCredentials() + .getAllTokens().iterator(); + while (tokenIterator.hasNext()) { + Token iteratedToken = tokenIterator.next(); + if (iteratedToken.getKind().equals(WasbDelegationTokenIdentifier.TOKEN_KIND)) { + delegationToken = iteratedToken.encodeToUrlString(); + } + } + } catch (IOException e) { + LOG.error("Error in fetching the WASB delegation token"); } - this.delegationToken = delegationToken; + try { + credServiceUrl = conf.get(Constants.KEY_CRED_SERVICE_URL, String + .format("http://%s:%s", + InetAddress.getLocalHost().getCanonicalHostName(), + Constants.DEFAULT_CRED_SERVICE_PORT)); + } catch (UnknownHostException e) { + LOG.error("Invalid CredService Url, configure it correctly."); + return false; + } if (credServiceUrl == null || credServiceUrl.isEmpty()) { LOG.error("CredService Url not found in configuration to initialize" @@ -119,16 +142,17 @@ public class RemoteSASKeyGeneratorImpl extends SASKeyGeneratorImpl { } remoteCallHelper = new WasbRemoteCallHelper(); - LOG.debug("Initialization of RemoteSASKeyGenerator instance successfull"); + this.isSecurityEnabled = UserGroupInformation.isSecurityEnabled(); + this.isKerberosSupportEnabled = conf.getBoolean( + Constants.AZURE_KERBEROS_SUPPORT_PROPERTY_NAME, false); + LOG.debug("Initialization of RemoteSASKeyGenerator instance successful"); return true; } @Override public URI getContainerSASUri(String storageAccount, String container) throws SASKeyGenerationException { - try { - LOG.debug("Generating Container SAS Key for Container {} " + "inside Storage Account {} ", container, storageAccount); URIBuilder uriBuilder = new URIBuilder(credServiceUrl); @@ -139,84 +163,131 @@ public class RemoteSASKeyGeneratorImpl extends SASKeyGeneratorImpl { container); uriBuilder.addParameter(SAS_EXPIRY_QUERY_PARAM_NAME, "" + getSasKeyExpiryPeriod()); - uriBuilder.addParameter(DELEGATION_TOKEN_QUERY_PARAM_NAME, - this.delegationToken); - - RemoteSASKeyGenerationResponse sasKeyResponse = - makeRemoteRequest(uriBuilder.build()); - - if (sasKeyResponse == null) { - throw new SASKeyGenerationException("RemoteSASKeyGenerationResponse" - + " object null from remote call"); - } else if (sasKeyResponse.getResponseCode() - == REMOTE_CALL_SUCCESS_CODE) { - return new URI(sasKeyResponse.getSasKey()); - } else { - throw new SASKeyGenerationException("Remote Service encountered error" - + " in SAS Key generation : " - + sasKeyResponse.getResponseMessage()); + if (isSecurityEnabled && (delegationToken != null && !delegationToken + .isEmpty())) { + uriBuilder.addParameter(DELEGATION_TOKEN_QUERY_PARAM_NAME, + this.delegationToken); } + + UserGroupInformation ugi = UserGroupInformation.getCurrentUser(); + UserGroupInformation connectUgi = ugi.getRealUser(); + if (connectUgi == null) { + connectUgi = ugi; + } else { + uriBuilder.addParameter(Constants.DOAS_PARAM, ugi.getShortUserName()); + } + + if(isSecurityEnabled && !connectUgi.hasKerberosCredentials()){ + connectUgi = UserGroupInformation.getLoginUser(); + } + return getSASKey(uriBuilder.build(), connectUgi); } catch (URISyntaxException uriSyntaxEx) { throw new SASKeyGenerationException("Encountered URISyntaxException " + "while building the HttpGetRequest to remote cred service", uriSyntaxEx); + } catch (IOException e) { + throw new SASKeyGenerationException("Encountered IOException" + + " while building the HttpGetRequest to remote service", e); } } @Override public URI getRelativeBlobSASUri(String storageAccount, String container, String relativePath) throws SASKeyGenerationException { - try { - LOG.debug("Generating RelativePath SAS Key for relativePath {} inside" + " Container {} inside Storage Account {} ", relativePath, container, storageAccount); URIBuilder uriBuilder = new URIBuilder(credServiceUrl); uriBuilder.setPath("/" + BLOB_SAS_OP); - uriBuilder.addParameter(STORAGE_ACCOUNT_QUERY_PARAM_NAME, - storageAccount); - uriBuilder.addParameter(CONTAINER_QUERY_PARAM_NAME, - container); + uriBuilder.addParameter(STORAGE_ACCOUNT_QUERY_PARAM_NAME, storageAccount); + uriBuilder.addParameter(CONTAINER_QUERY_PARAM_NAME, container); uriBuilder.addParameter(RELATIVE_PATH_QUERY_PARAM_NAME, relativePath); uriBuilder.addParameter(SAS_EXPIRY_QUERY_PARAM_NAME, "" + getSasKeyExpiryPeriod()); - uriBuilder.addParameter(DELEGATION_TOKEN_QUERY_PARAM_NAME, - this.delegationToken); - RemoteSASKeyGenerationResponse sasKeyResponse = - makeRemoteRequest(uriBuilder.build()); - - if (sasKeyResponse == null) { - throw new SASKeyGenerationException("RemoteSASKeyGenerationResponse" - + " object null from remote call"); - } else if (sasKeyResponse.getResponseCode() - == REMOTE_CALL_SUCCESS_CODE) { - return new URI(sasKeyResponse.getSasKey()); - } else { - throw new SASKeyGenerationException("Remote Service encountered error" - + " in SAS Key generation : " - + sasKeyResponse.getResponseMessage()); + if (isSecurityEnabled && (delegationToken != null && !delegationToken + .isEmpty())) { + uriBuilder.addParameter(DELEGATION_TOKEN_QUERY_PARAM_NAME, + this.delegationToken); } + + UserGroupInformation ugi = UserGroupInformation.getCurrentUser(); + UserGroupInformation connectUgi = ugi.getRealUser(); + if (connectUgi == null) { + connectUgi = ugi; + } else{ + uriBuilder.addParameter(Constants.DOAS_PARAM, ugi.getShortUserName()); + } + + if(isSecurityEnabled && !connectUgi.hasKerberosCredentials()){ + connectUgi = UserGroupInformation.getLoginUser(); + } + return getSASKey(uriBuilder.build(), connectUgi); } catch (URISyntaxException uriSyntaxEx) { throw new SASKeyGenerationException("Encountered URISyntaxException" + " while building the HttpGetRequest to " + " remote service", uriSyntaxEx); + } catch (IOException e) { + throw new SASKeyGenerationException("Encountered IOException" + + " while building the HttpGetRequest to remote service", e); + } + } + + private URI getSASKey(final URI uri, UserGroupInformation connectUgi) + throws URISyntaxException, SASKeyGenerationException { + RemoteSASKeyGenerationResponse sasKeyResponse = null; + try { + connectUgi.checkTGTAndReloginFromKeytab(); + sasKeyResponse = connectUgi.doAs(new PrivilegedExceptionAction() { + @Override + public RemoteSASKeyGenerationResponse run() throws Exception { + AuthenticatedURL.Token token = null; + if (isKerberosSupportEnabled && UserGroupInformation.isSecurityEnabled() && ( + delegationToken == null || delegationToken.isEmpty())) { + token = new AuthenticatedURL.Token(); + final Authenticator kerberosAuthenticator = new KerberosDelegationTokenAuthenticator(); + kerberosAuthenticator.authenticate(uri.toURL(), token); + Validate.isTrue(token.isSet(), + "Authenticated Token is NOT present. The request cannot proceed."); + } + return makeRemoteRequest(uri, (token != null ? token.toString() : null)); + } + }); + } catch (InterruptedException e) { + LOG.error("Error fetching the SAS Key from Remote Service", e); + } catch (IOException e) { + LOG.error("Error fetching the SAS Key from Remote Service", e); + } + + if (sasKeyResponse == null) { + throw new SASKeyGenerationException( + "RemoteSASKeyGenerationResponse" + " object null from remote call"); + } else if (sasKeyResponse.getResponseCode() == REMOTE_CALL_SUCCESS_CODE) { + return new URI(sasKeyResponse.getSasKey()); + } else { + throw new SASKeyGenerationException("Remote Service encountered error" + + " in SAS Key generation : " + sasKeyResponse.getResponseMessage()); } } /** * Helper method to make a remote request. * @param uri - Uri to use for the remote request + * @param token - hadoop.auth token for the remote request * @return RemoteSASKeyGenerationResponse */ - private RemoteSASKeyGenerationResponse makeRemoteRequest(URI uri) + private RemoteSASKeyGenerationResponse makeRemoteRequest(URI uri, String token) throws SASKeyGenerationException { try { + HttpGet httpGet = new HttpGet(uri); + if(token != null){ + httpGet.setHeader("Cookie", AuthenticatedURL.AUTH_COOKIE + "=" + token); + } String responseBody = - remoteCallHelper.makeRemoteGetRequest(new HttpGet(uri)); + remoteCallHelper.makeRemoteGetRequest(httpGet); ObjectMapper objectMapper = new ObjectMapper(); return objectMapper.readValue(responseBody, diff --git a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/RemoteWasbAuthorizerImpl.java b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/RemoteWasbAuthorizerImpl.java new file mode 100644 index 00000000000..bb1093d3ff2 --- /dev/null +++ b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/RemoteWasbAuthorizerImpl.java @@ -0,0 +1,247 @@ +/** + * 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.fs.azure; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.URISyntaxException; +import java.security.PrivilegedExceptionAction; +import java.util.Iterator; + +import org.apache.commons.lang.Validate; +import org.apache.hadoop.conf.Configuration; + +import org.apache.hadoop.fs.azure.security.Constants; +import org.apache.hadoop.fs.azure.security.WasbDelegationTokenIdentifier; +import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.security.authentication.client.AuthenticatedURL; +import org.apache.hadoop.security.authentication.client.Authenticator; +import org.apache.hadoop.security.token.Token; +import org.apache.hadoop.security.token.TokenIdentifier; +import org.apache.hadoop.security.token.delegation.web.KerberosDelegationTokenAuthenticator; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.utils.URIBuilder; + +import com.fasterxml.jackson.core.JsonParseException; +import org.codehaus.jackson.map.JsonMappingException; +import org.codehaus.jackson.map.ObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.apache.hadoop.fs.azure.WasbRemoteCallHelper.REMOTE_CALL_SUCCESS_CODE; + +/** + * Class implementing WasbAuthorizerInterface using a remote + * service that implements the authorization operation. This + * class expects the url of the remote service to be passed + * via config. + */ +public class RemoteWasbAuthorizerImpl implements WasbAuthorizerInterface { + + public static final Logger LOG = + LoggerFactory.getLogger(RemoteWasbAuthorizerImpl.class); + private String remoteAuthorizerServiceUrl = ""; + + /** + * Configuration parameter name expected in the Configuration object to + * provide the url of the remote service. {@value} + */ + public static final String KEY_REMOTE_AUTH_SERVICE_URL = + "fs.azure.authorization.remote.service.url"; + + /** + * Authorization operation OP name in the remote service {@value} + */ + private static final String CHECK_AUTHORIZATION_OP = + "CHECK_AUTHORIZATION"; + + /** + * Query parameter specifying the access operation type. {@value} + */ + private static final String ACCESS_OPERATION_QUERY_PARAM_NAME = + "operation_type"; + + /** + * Query parameter specifying the wasb absolute path. {@value} + */ + private static final String WASB_ABSOLUTE_PATH_QUERY_PARAM_NAME = + "wasb_absolute_path"; + + /** + * Query parameter name for user info {@value} + */ + private static final String DELEGATION_TOKEN_QUERY_PARAM_NAME = + "delegation"; + + private WasbRemoteCallHelper remoteCallHelper = null; + private String delegationToken; + private boolean isSecurityEnabled; + private boolean isKerberosSupportEnabled; + + @Override + public void init(Configuration conf) + throws WasbAuthorizationException, IOException { + LOG.debug("Initializing RemoteWasbAuthorizerImpl instance"); + delegationToken = UserGroupInformation.getCurrentUser().getCredentials().getToken(WasbDelegationTokenIdentifier.TOKEN_KIND).encodeToUrlString(); + + remoteAuthorizerServiceUrl = conf.get(KEY_REMOTE_AUTH_SERVICE_URL, String + .format("http://%s:%s", + InetAddress.getLocalHost().getCanonicalHostName(), + Constants.DEFAULT_CRED_SERVICE_PORT)); + + if (remoteAuthorizerServiceUrl == null + || remoteAuthorizerServiceUrl.isEmpty()) { + throw new WasbAuthorizationException( + "fs.azure.authorization.remote.service.url config not set" + + " in configuration."); + } + + this.remoteCallHelper = new WasbRemoteCallHelper(); + this.isSecurityEnabled = UserGroupInformation.isSecurityEnabled(); + this.isKerberosSupportEnabled = conf.getBoolean( + Constants.AZURE_KERBEROS_SUPPORT_PROPERTY_NAME, false); + } + + @Override + public boolean authorize(String wasbAbsolutePath, String accessType) + throws WasbAuthorizationException, IOException { + try { + final URIBuilder uriBuilder = new URIBuilder(remoteAuthorizerServiceUrl); + uriBuilder.setPath("/" + CHECK_AUTHORIZATION_OP); + uriBuilder.addParameter(WASB_ABSOLUTE_PATH_QUERY_PARAM_NAME, + wasbAbsolutePath); + uriBuilder.addParameter(ACCESS_OPERATION_QUERY_PARAM_NAME, + accessType); + if (isSecurityEnabled && (delegationToken != null && !delegationToken + .isEmpty())) { + uriBuilder + .addParameter(DELEGATION_TOKEN_QUERY_PARAM_NAME, delegationToken); + } + String responseBody = null; + UserGroupInformation ugi = UserGroupInformation.getCurrentUser(); + UserGroupInformation connectUgi = ugi.getRealUser(); + if (connectUgi == null) { + connectUgi = ugi; + } else{ + uriBuilder.addParameter(Constants.DOAS_PARAM, ugi.getShortUserName()); + } + if(isSecurityEnabled && !connectUgi.hasKerberosCredentials()){ + connectUgi = UserGroupInformation.getLoginUser(); + } + connectUgi.checkTGTAndReloginFromKeytab(); + try { + responseBody = connectUgi.doAs(new PrivilegedExceptionAction(){ + @Override + public String run() throws Exception { + AuthenticatedURL.Token token = null; + HttpGet httpGet = new HttpGet(uriBuilder.build()); + if (isKerberosSupportEnabled && UserGroupInformation.isSecurityEnabled() && ( + delegationToken == null || delegationToken.isEmpty())) { + token = new AuthenticatedURL.Token(); + final Authenticator kerberosAuthenticator = new KerberosDelegationTokenAuthenticator(); + kerberosAuthenticator + .authenticate(uriBuilder.build().toURL(), token); + Validate.isTrue(token.isSet(), + "Authenticated Token is NOT present. The request cannot proceed."); + if(token != null){ + httpGet.setHeader("Cookie", AuthenticatedURL.AUTH_COOKIE + "=" + token); + } + } + return remoteCallHelper.makeRemoteGetRequest(httpGet); + }}); + } catch (InterruptedException e) { + LOG.error("Error in check authorization", e); + } + + ObjectMapper objectMapper = new ObjectMapper(); + RemoteAuthorizerResponse authorizerResponse = + objectMapper.readValue(responseBody, RemoteAuthorizerResponse.class); + + if (authorizerResponse == null) { + throw new WasbAuthorizationException( + "RemoteAuthorizerResponse object null from remote call"); + } else if (authorizerResponse.getResponseCode() + == REMOTE_CALL_SUCCESS_CODE) { + return authorizerResponse.getAuthorizationResult(); + } else { + throw new WasbAuthorizationException("Remote authorization" + + " serivce encountered an error " + + authorizerResponse.getResponseMessage()); + } + } catch (URISyntaxException | WasbRemoteCallException + | JsonParseException | JsonMappingException ex) { + throw new WasbAuthorizationException(ex); + } + } +} + +/** + * POJO representing the response expected from a remote + * authorization service. + * The remote service is expected to return the authorization + * response in the following JSON format + * { + * "responseCode" : 0 or non-zero , + * "responseMessage" : relavant message of failure + * "authorizationResult" : authorization result + * true - if auhorization allowed + * false - otherwise. + * + * } + */ +class RemoteAuthorizerResponse { + + private int responseCode; + private boolean authorizationResult; + private String responseMessage; + + public RemoteAuthorizerResponse(){ + } + + public RemoteAuthorizerResponse(int responseCode, + boolean authorizationResult, String message) { + this.responseCode = responseCode; + this.authorizationResult = authorizationResult; + this.responseMessage = message; + } + + public int getResponseCode() { + return responseCode; + } + + public void setResponseCode(int responseCode) { + this.responseCode = responseCode; + } + + public boolean getAuthorizationResult() { + return authorizationResult; + } + + public void setAuthorizationResult(boolean authorizationResult) { + this.authorizationResult = authorizationResult; + } + + public String getResponseMessage() { + return responseMessage; + } + + public void setResponseMessage(String message) { + this.responseMessage = message; + } +} \ No newline at end of file diff --git a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/SecureStorageInterfaceImpl.java b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/SecureStorageInterfaceImpl.java index 6749a76f7f4..5ec9136a9a5 100644 --- a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/SecureStorageInterfaceImpl.java +++ b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/SecureStorageInterfaceImpl.java @@ -69,19 +69,17 @@ public class SecureStorageInterfaceImpl extends StorageInterface { public static final String SAS_ERROR_CODE = "SAS Error"; private SASKeyGeneratorInterface sasKeyGenerator; private String storageAccount; - private String delegationToken; public SecureStorageInterfaceImpl(boolean useLocalSASKeyMode, - Configuration conf, String delegationToken) + Configuration conf) throws SecureModeException { - this.delegationToken = delegationToken; if (useLocalSASKeyMode) { this.sasKeyGenerator = new LocalSASKeyGeneratorImpl(conf); } else { RemoteSASKeyGeneratorImpl remoteSasKeyGenerator = new RemoteSASKeyGeneratorImpl(conf); - if (!remoteSasKeyGenerator.initialize(conf, this.delegationToken)) { + if (!remoteSasKeyGenerator.initialize(conf)) { throw new SecureModeException("Remote SAS Key mode could" + " not be initialized"); } diff --git a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/WasbAuthorizationException.java b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/WasbAuthorizationException.java new file mode 100644 index 00000000000..eff9248dffe --- /dev/null +++ b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/WasbAuthorizationException.java @@ -0,0 +1,40 @@ +/** + * 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.fs.azure; + +/** + * Exception that gets thrown during the authorization failures + * in WASB. + */ +public class WasbAuthorizationException extends AzureException { + + private static final long serialVersionUID = 1L; + + public WasbAuthorizationException(String message) { + super(message); + } + + public WasbAuthorizationException(String message, Throwable cause) { + super(message, cause); + } + + public WasbAuthorizationException(Throwable t) { + super(t); + } +} \ No newline at end of file diff --git a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/WasbAuthorizationOperations.java b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/WasbAuthorizationOperations.java new file mode 100644 index 00000000000..bd768372215 --- /dev/null +++ b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/WasbAuthorizationOperations.java @@ -0,0 +1,44 @@ +/** + * 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.fs.azure; + +/** + * Different authorization operations supported + * in WASB. + */ + +public enum WasbAuthorizationOperations { + + READ, WRITE, EXECUTE; + + @Override + public String toString() { + switch(this) { + case READ: + return "read"; + case WRITE: + return "write"; + case EXECUTE: + return "execute"; + default: + throw new IllegalArgumentException( + "Invalid Auhtorization Operation"); + } + } +} \ No newline at end of file diff --git a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/WasbAuthorizerInterface.java b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/WasbAuthorizerInterface.java new file mode 100644 index 00000000000..0c61997a61e --- /dev/null +++ b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/WasbAuthorizerInterface.java @@ -0,0 +1,47 @@ +/** + * 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.fs.azure; + +import java.io.IOException; + +import org.apache.hadoop.conf.Configuration; + +/** + * Interface to implement authorization support in WASB. + * API's of this interface will be implemented in the + * StorageInterface Layer before making calls to Azure + * Storage. + */ +public interface WasbAuthorizerInterface { + /** + * Initializer method + * @param conf - Configuration object + */ + public void init(Configuration conf) + throws WasbAuthorizationException, IOException; + + /** + * Authorizer API to authorize access in WASB. + * @param wasbAbsolutePath : Absolute WASB Path used for access. + * @param accessType : Type of access + * @return : true - If access allowed false - If access is not allowed. + */ + public boolean authorize(String wasbAbsolutePath, String accessType) + throws WasbAuthorizationException, IOException; +} \ No newline at end of file diff --git a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/security/Constants.java b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/security/Constants.java new file mode 100644 index 00000000000..105068710d5 --- /dev/null +++ b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/security/Constants.java @@ -0,0 +1,54 @@ +/** + * 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.fs.azure.security; + +/** + * Constants for used with WASB security implementation. + */ +public final class Constants { + + private Constants() { + } + + /** + * Configuration parameter name expected in the Configuration + * object to provide the url of the remote service {@value} + */ + public static final String KEY_CRED_SERVICE_URL = "fs.azure.cred.service.url"; + /** + * Default port of the remote service used as delegation token manager and Azure storage SAS key generator. + */ + public static final int DEFAULT_CRED_SERVICE_PORT = 50911; + + /** + * Default remote delegation token manager endpoint. + */ + public static final String DEFAULT_DELEGATION_TOKEN_MANAGER_ENDPOINT = "/tokenmanager/v1"; + + /** + * The configuration property to enable Kerberos support. + */ + + public static final String AZURE_KERBEROS_SUPPORT_PROPERTY_NAME = "fs.azure.enable.kerberos.support"; + + /** + * Parameter to be used for impersonation. + */ + public static final String DOAS_PARAM="doas"; +} diff --git a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/security/WasbDelegationTokenIdentifier.java b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/security/WasbDelegationTokenIdentifier.java new file mode 100644 index 00000000000..530e04572e2 --- /dev/null +++ b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/security/WasbDelegationTokenIdentifier.java @@ -0,0 +1,48 @@ +/** + * 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.fs.azure.security; + +import org.apache.hadoop.io.Text; +import org.apache.hadoop.security.token.delegation.web.DelegationTokenIdentifier; + +/** + * Delegation token Identifier for WASB delegation tokens. + */ +public class WasbDelegationTokenIdentifier extends DelegationTokenIdentifier { + public static final Text TOKEN_KIND = new Text("WASB delegation"); + + public WasbDelegationTokenIdentifier(){ + super(TOKEN_KIND); + } + + public WasbDelegationTokenIdentifier(Text kind) { + super(kind); + } + + public WasbDelegationTokenIdentifier(Text kind, Text owner, Text renewer, + Text realUser) { + super(kind, owner, renewer, realUser); + } + + @Override + public Text getKind() { + return TOKEN_KIND; + } + +} diff --git a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/security/WasbTokenRenewer.java b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/security/WasbTokenRenewer.java new file mode 100644 index 00000000000..09b7349e120 --- /dev/null +++ b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/security/WasbTokenRenewer.java @@ -0,0 +1,124 @@ +/** + * 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.fs.azure.security; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.io.Text; +import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.security.token.Token; +import org.apache.hadoop.security.token.TokenRenewer; +import org.apache.hadoop.security.token.delegation.AbstractDelegationTokenIdentifier; +import org.apache.hadoop.security.token.delegation.web.DelegationTokenAuthenticatedURL; +import org.apache.hadoop.security.token.delegation.web.DelegationTokenAuthenticator; +import org.apache.hadoop.security.token.delegation.web.KerberosDelegationTokenAuthenticator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.URL; +import java.security.PrivilegedExceptionAction; + +/** + * Token Renewer for renewing WASB delegation tokens with remote service. + */ +public class WasbTokenRenewer extends TokenRenewer { + public static final Logger LOG = LoggerFactory + .getLogger(WasbTokenRenewer.class); + + @Override + public boolean handleKind(Text kind) { + return WasbDelegationTokenIdentifier.TOKEN_KIND.equals(kind); + } + + @Override + public boolean isManaged(Token token) throws IOException { + return true; + } + + @Override + public long renew(final Token token, Configuration conf) + throws IOException, InterruptedException { + LOG.debug("Renewing the delegation token"); + final UserGroupInformation ugi = UserGroupInformation.getCurrentUser(); + UserGroupInformation connectUgi = ugi.getRealUser(); + final UserGroupInformation proxyUser = connectUgi; + if (connectUgi == null) { + connectUgi = ugi; + } + if(!connectUgi.hasKerberosCredentials()){ + connectUgi = UserGroupInformation.getLoginUser(); + } + connectUgi.checkTGTAndReloginFromKeytab(); + final DelegationTokenAuthenticatedURL.Token authToken = new DelegationTokenAuthenticatedURL.Token(); + authToken + .setDelegationToken((Token) token); + final String credServiceUrl = conf.get(Constants.KEY_CRED_SERVICE_URL, + String.format("http://%s:%s", + InetAddress.getLocalHost().getCanonicalHostName(), + Constants.DEFAULT_CRED_SERVICE_PORT)); + DelegationTokenAuthenticator authenticator = new KerberosDelegationTokenAuthenticator(); + final DelegationTokenAuthenticatedURL authURL = new DelegationTokenAuthenticatedURL( + authenticator); + + return connectUgi.doAs(new PrivilegedExceptionAction() { + @Override + public Long run() throws Exception { + return authURL.renewDelegationToken(new URL(credServiceUrl + + Constants.DEFAULT_DELEGATION_TOKEN_MANAGER_ENDPOINT), + authToken, (proxyUser != null) ? ugi.getShortUserName() : null); + } + }); + } + + @Override + public void cancel(final Token token, Configuration conf) + throws IOException, InterruptedException { + LOG.debug("Cancelling the delegation token"); + final UserGroupInformation ugi = UserGroupInformation.getCurrentUser(); + UserGroupInformation connectUgi = ugi.getRealUser(); + final UserGroupInformation proxyUser = connectUgi; + if (connectUgi == null) { + connectUgi = ugi; + } + if(!connectUgi.hasKerberosCredentials()){ + connectUgi = UserGroupInformation.getLoginUser(); + } + connectUgi.checkTGTAndReloginFromKeytab(); + final DelegationTokenAuthenticatedURL.Token authToken = new DelegationTokenAuthenticatedURL.Token(); + authToken + .setDelegationToken((Token) token); + final String credServiceUrl = conf.get(Constants.KEY_CRED_SERVICE_URL, + String.format("http://%s:%s", + InetAddress.getLocalHost().getCanonicalHostName(), + Constants.DEFAULT_CRED_SERVICE_PORT)); + DelegationTokenAuthenticator authenticator = new KerberosDelegationTokenAuthenticator(); + final DelegationTokenAuthenticatedURL authURL = new DelegationTokenAuthenticatedURL( + authenticator); + connectUgi.doAs(new PrivilegedExceptionAction() { + @Override + public Void run() throws Exception { + authURL.cancelDelegationToken(new URL(credServiceUrl + + Constants.DEFAULT_DELEGATION_TOKEN_MANAGER_ENDPOINT), + authToken, (proxyUser != null) ? ugi.getShortUserName() : null); + return null; + } + }); + } +} \ No newline at end of file diff --git a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/security/package.html b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/security/package.html new file mode 100644 index 00000000000..fe58c0a272a --- /dev/null +++ b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/security/package.html @@ -0,0 +1,28 @@ + + + + + + +

+ Infrastructure for WASB client Security to work with Kerberos and Delegation + tokens. +

+ + + diff --git a/hadoop-tools/hadoop-azure/src/main/resources/META-INF/services/org.apache.hadoop.security.token.TokenIdentifier b/hadoop-tools/hadoop-azure/src/main/resources/META-INF/services/org.apache.hadoop.security.token.TokenIdentifier new file mode 100644 index 00000000000..7ec8216deb0 --- /dev/null +++ b/hadoop-tools/hadoop-azure/src/main/resources/META-INF/services/org.apache.hadoop.security.token.TokenIdentifier @@ -0,0 +1,16 @@ +# 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. + +org.apache.hadoop.fs.azure.security.WasbDelegationTokenIdentifier \ No newline at end of file diff --git a/hadoop-tools/hadoop-azure/src/main/resources/META-INF/services/org.apache.hadoop.security.token.TokenRenewer b/hadoop-tools/hadoop-azure/src/main/resources/META-INF/services/org.apache.hadoop.security.token.TokenRenewer new file mode 100644 index 00000000000..f9c590aad8d --- /dev/null +++ b/hadoop-tools/hadoop-azure/src/main/resources/META-INF/services/org.apache.hadoop.security.token.TokenRenewer @@ -0,0 +1,16 @@ +# 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. + +org.apache.hadoop.fs.azure.security.WasbTokenRenewer \ No newline at end of file diff --git a/hadoop-tools/hadoop-azure/src/site/markdown/index.md b/hadoop-tools/hadoop-azure/src/site/markdown/index.md index 2865223acdb..a06d297ea7c 100644 --- a/hadoop-tools/hadoop-azure/src/site/markdown/index.md +++ b/hadoop-tools/hadoop-azure/src/site/markdown/index.md @@ -333,6 +333,40 @@ The service is expected to return a response in JSON format: "sasKey" : Requested SAS Key } ``` + +## Authorization Support in WASB. + +Authorization support can be enabled in WASB using the following configuration: + +``` + + fs.azure.authorization + true + +``` + The current implementation of authorization relies on the presence of an external service that can enforce + the authorization. The service is expected to be running on a URL provided by the following config. + +``` + + fs.azure.authorization.remote.service.url + {URL} + +``` + + The remote service is expected to provide support for the following REST call: ```{URL}/CHECK_AUTHORIZATION``` + An example request: + ```{URL}/CHECK_AUTHORIZATION?wasb_absolute_path=&operation_type=&delegation_token=``` + + The service is expected to return a response in JSON format: + ``` + { + "responseCode" : 0 or non-zero , + "responseMessage" : relavant message on failure , + "authorizationResult" : true/false + } + ``` + ## Testing the hadoop-azure Module The hadoop-azure module includes a full suite of unit tests. Most of the tests diff --git a/hadoop-tools/hadoop-azure/src/test/java/org/apache/hadoop/fs/azure/MockWasbAuthorizerImpl.java b/hadoop-tools/hadoop-azure/src/test/java/org/apache/hadoop/fs/azure/MockWasbAuthorizerImpl.java new file mode 100644 index 00000000000..af5a537ce4d --- /dev/null +++ b/hadoop-tools/hadoop-azure/src/test/java/org/apache/hadoop/fs/azure/MockWasbAuthorizerImpl.java @@ -0,0 +1,102 @@ +/** + * 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.fs.azure; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.hadoop.conf.Configuration; + +/** + * A mock wasb authorizer implementation. + */ + +public class MockWasbAuthorizerImpl implements WasbAuthorizerInterface { + + private Map authRules; + + @Override + public void init(Configuration conf) { + authRules = new HashMap(); + } + + public void addAuthRule(String wasbAbsolutePath, + String accessType, boolean access) { + AuthorizationComponent component = + new AuthorizationComponent(wasbAbsolutePath, accessType); + this.authRules.put(component, access); + } + + @Override + public boolean authorize(String wasbAbsolutePath, String accessType) + throws WasbAuthorizationException { + + AuthorizationComponent component = + new AuthorizationComponent(wasbAbsolutePath, accessType); + + if (authRules.containsKey(component)) { + return authRules.get(component); + } else { + return false; + } + } +} + +class AuthorizationComponent { + + private String wasbAbsolutePath; + private String accessType; + + public AuthorizationComponent(String wasbAbsolutePath, + String accessType) { + this.wasbAbsolutePath = wasbAbsolutePath; + this.accessType = accessType; + } + + @Override + public int hashCode() { + return this.wasbAbsolutePath.hashCode() ^ this.accessType.hashCode(); + } + + @Override + public boolean equals(Object obj) { + + if (obj == this) { + return true; + } + + if (obj == null + || !(obj instanceof AuthorizationComponent)) { + return false; + } + + return ((AuthorizationComponent)obj). + getWasbAbsolutePath().equals(this.wasbAbsolutePath) + && ((AuthorizationComponent)obj). + getAccessType().equals(this.accessType); + } + + public String getWasbAbsolutePath() { + return this.wasbAbsolutePath; + } + + public String getAccessType() { + return accessType; + } +} \ No newline at end of file diff --git a/hadoop-tools/hadoop-azure/src/test/java/org/apache/hadoop/fs/azure/TestNativeAzureFileSystemAuthorization.java b/hadoop-tools/hadoop-azure/src/test/java/org/apache/hadoop/fs/azure/TestNativeAzureFileSystemAuthorization.java new file mode 100644 index 00000000000..e76533550b4 --- /dev/null +++ b/hadoop-tools/hadoop-azure/src/test/java/org/apache/hadoop/fs/azure/TestNativeAzureFileSystemAuthorization.java @@ -0,0 +1,277 @@ +/** + * 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.fs.azure; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.FSDataInputStream; +import org.apache.hadoop.fs.Path; +import org.junit.Test; + +import com.sun.tools.javac.util.Assert; + +/** + * Test class to hold all WASB authorization tests. + */ +public class TestNativeAzureFileSystemAuthorization + extends AbstractWasbTestBase { + + @Override + protected AzureBlobStorageTestAccount createTestAccount() throws Exception { + Configuration conf = new Configuration(); + conf.set(NativeAzureFileSystem.KEY_AZURE_AUTHORIZATION, "true"); + conf.set(RemoteWasbAuthorizerImpl.KEY_REMOTE_AUTH_SERVICE_URL, "test_url"); + return AzureBlobStorageTestAccount.create(conf); + } + + /** + * Positive test to verify Create and delete access check + * @throws Throwable + */ + @Test + public void testCreateAccessCheckPositive() throws Throwable { + + AzureBlobStorageTestAccount testAccount = createTestAccount(); + NativeAzureFileSystem fs = testAccount.getFileSystem(); + + String testFile = "test.dat"; + Path testPath = new Path(fs.getWorkingDirectory(), testFile); + + MockWasbAuthorizerImpl authorizer = new MockWasbAuthorizerImpl(); + authorizer.init(null); + authorizer.addAuthRule(testPath.toString(), + WasbAuthorizationOperations.WRITE.toString(), true); + authorizer.addAuthRule(testPath.toString(), + WasbAuthorizationOperations.EXECUTE.toString(), true); + fs.updateWasbAuthorizer(authorizer); + authorizer.addAuthRule(fs.getWorkingDirectory().toString(), + WasbAuthorizationOperations.EXECUTE.toString(), true); + + fs.create(testPath); + Assert.check(fs.exists(testPath)); + fs.delete(testPath, false); + } + + /** + * Negative test to verify Create access check + * @throws Throwable + */ + + @Test(expected=WasbAuthorizationException.class) + public void testCreateAccessCheckNegative() throws Throwable { + + AzureBlobStorageTestAccount testAccount = createTestAccount(); + NativeAzureFileSystem fs = testAccount.getFileSystem(); + + String testFile = "test.dat"; + Path testPath = new Path(fs.getWorkingDirectory(), testFile); + + MockWasbAuthorizerImpl authorizer = new MockWasbAuthorizerImpl(); + authorizer.init(null); + authorizer.addAuthRule(testPath.toString(), + WasbAuthorizationOperations.WRITE.toString(), false); + fs.updateWasbAuthorizer(authorizer); + + fs.create(new Path(testFile)); + } + + /** + * Positive test to verify Create and delete access check + * @throws Throwable + */ + @Test + public void testListAccessCheckPositive() throws Throwable { + + AzureBlobStorageTestAccount testAccount = createTestAccount(); + NativeAzureFileSystem fs = testAccount.getFileSystem(); + + String testFolder = "\\"; + Path testPath = new Path(fs.getWorkingDirectory(), testFolder); + + MockWasbAuthorizerImpl authorizer = new MockWasbAuthorizerImpl(); + authorizer.init(null); + authorizer.addAuthRule(testPath.toString(), + WasbAuthorizationOperations.EXECUTE.toString(), true); + fs.updateWasbAuthorizer(authorizer); + + fs.listStatus(testPath); + } + + /** + * Negative test to verify Create access check + * @throws Throwable + */ + + @Test(expected=WasbAuthorizationException.class) + public void testListAccessCheckNegative() throws Throwable { + + AzureBlobStorageTestAccount testAccount = createTestAccount(); + NativeAzureFileSystem fs = testAccount.getFileSystem(); + + String testFolder = "\\"; + Path testPath = new Path(fs.getWorkingDirectory(), testFolder); + + MockWasbAuthorizerImpl authorizer = new MockWasbAuthorizerImpl(); + authorizer.init(null); + authorizer.addAuthRule(testPath.toString(), + WasbAuthorizationOperations.EXECUTE.toString(), false); + fs.updateWasbAuthorizer(authorizer); + + fs.listStatus(testPath); + } + + /** + * Positive test to verify rename access check. + * @throws Throwable + */ + @Test + public void testRenameAccessCheckPositive() throws Throwable { + + AzureBlobStorageTestAccount testAccount = createTestAccount(); + NativeAzureFileSystem fs = testAccount.getFileSystem(); + + String testFile = "test.dat"; + Path testPath = new Path(fs.getWorkingDirectory(), testFile); + String renameFile = "test2.dat"; + Path renamePath = new Path(fs.getWorkingDirectory(), renameFile); + + MockWasbAuthorizerImpl authorizer = new MockWasbAuthorizerImpl(); + authorizer.init(null); + authorizer.addAuthRule(testPath.toString(), + WasbAuthorizationOperations.WRITE.toString(), true); + authorizer.addAuthRule(testPath.toString(), + WasbAuthorizationOperations.EXECUTE.toString(), true); + authorizer.addAuthRule(renamePath.toString(), + WasbAuthorizationOperations.EXECUTE.toString(), true); + authorizer.addAuthRule(fs.getWorkingDirectory().toString(), + WasbAuthorizationOperations.EXECUTE.toString(), true); + fs.updateWasbAuthorizer(authorizer); + fs.create(testPath); + + Assert.check(fs.exists(testPath)); + fs.rename(testPath, renamePath); + Assert.check(fs.exists(renamePath)); + fs.delete(renamePath, false); + } + + /** + * Negative test to verify rename access check. + * @throws Throwable + */ + @Test(expected=WasbAuthorizationException.class) + public void testRenameAccessCheckNegative() throws Throwable { + + AzureBlobStorageTestAccount testAccount = createTestAccount(); + NativeAzureFileSystem fs = testAccount.getFileSystem(); + String testFile = "test.dat"; + Path testPath = new Path(fs.getWorkingDirectory(), testFile); + Path renamePath = new Path("test2.dat"); + + MockWasbAuthorizerImpl authorizer = new MockWasbAuthorizerImpl(); + authorizer.init(null); + authorizer.addAuthRule(testPath.toString(), + WasbAuthorizationOperations.WRITE.toString(), true); + authorizer.addAuthRule(testPath.toString(), + WasbAuthorizationOperations.EXECUTE.toString(), false); + fs.updateWasbAuthorizer(authorizer); + + try { + fs.create(testPath); + + Assert.check(fs.exists(testPath)); + fs.rename(testPath, renamePath); + Assert.check(fs.exists(renamePath)); + fs.delete(renamePath, false); + } catch (WasbAuthorizationException ex) { + throw ex; + } finally { + authorizer = new MockWasbAuthorizerImpl(); + authorizer.init(null); + authorizer.addAuthRule(testPath.toString(), + WasbAuthorizationOperations.EXECUTE.toString(), false); + fs.updateWasbAuthorizer(authorizer); + Assert.check(fs.exists(testPath)); + fs.delete(testPath, false); + } + } + + /** + * Positive test for read access check. + * @throws Throwable + */ + @Test + public void testReadAccessCheckPositive() throws Throwable { + + AzureBlobStorageTestAccount testAccount = createTestAccount(); + NativeAzureFileSystem fs = testAccount.getFileSystem(); + String testFile = "test.dat"; + Path testPath = new Path(fs.getWorkingDirectory(), testFile); + MockWasbAuthorizerImpl authorizer = new MockWasbAuthorizerImpl(); + authorizer.init(null); + authorizer.addAuthRule(testPath.toString(), + WasbAuthorizationOperations.WRITE.toString(), true); + authorizer.addAuthRule(testPath.toString(), + WasbAuthorizationOperations.EXECUTE.toString(), true); + authorizer.addAuthRule(testPath.toString(), + WasbAuthorizationOperations.READ.toString(), true); + authorizer.addAuthRule(fs.getWorkingDirectory().toString(), + WasbAuthorizationOperations.EXECUTE.toString(), true); + fs.updateWasbAuthorizer(authorizer); + fs.create(testPath); + Assert.check(fs.exists(testPath)); + FSDataInputStream inputStream = fs.open(testPath); + inputStream.close(); + fs.delete(testPath, false); + } + + /** + * Negative test to verify read access check. + * @throws Throwable + */ + @Test(expected=WasbAuthorizationException.class) + public void testReadAccessCheckNegative() throws Throwable { + + AzureBlobStorageTestAccount testAccount = createTestAccount(); + NativeAzureFileSystem fs = testAccount.getFileSystem(); + String testFile = "test.dat"; + Path testPath = new Path(fs.getWorkingDirectory(), testFile); + MockWasbAuthorizerImpl authorizer = new MockWasbAuthorizerImpl(); + authorizer.init(null); + authorizer.addAuthRule(testPath.toString(), + WasbAuthorizationOperations.WRITE.toString(), true); + authorizer.addAuthRule(testPath.toString(), + WasbAuthorizationOperations.EXECUTE.toString(), true); + authorizer.addAuthRule(testPath.toString(), + WasbAuthorizationOperations.READ.toString(), false); + fs.updateWasbAuthorizer(authorizer); + + fs.create(new Path(testFile)); + Assert.check(fs.exists(testPath)); + FSDataInputStream inputStream = null; + try { + inputStream = fs.open(new Path(testFile)); + } catch (WasbAuthorizationException ex) { + throw ex; + } finally { + fs.delete(new Path(testFile), false); + if (inputStream != null) { + inputStream.close(); + } + } + } +} \ No newline at end of file From 0f336bab9c070e0cdc935df4305084a036e2def7 Mon Sep 17 00:00:00 2001 From: Mingliang Liu Date: Fri, 3 Mar 2017 18:47:13 -0800 Subject: [PATCH 007/188] HDFS-11494. Log message when DN is not selected for block replication. Contributed by Yiqun Lin --- .../hdfs/server/blockmanagement/DatanodeDescriptor.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/blockmanagement/DatanodeDescriptor.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/blockmanagement/DatanodeDescriptor.java index 15af6ab1a7f..7b3e4e17d5d 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/blockmanagement/DatanodeDescriptor.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/blockmanagement/DatanodeDescriptor.java @@ -687,6 +687,10 @@ public class DatanodeDescriptor extends DatanodeInfo { } } if (requiredSize > remaining - scheduledSize) { + LOG.debug( + "The node {} does not have enough {} space (required={}," + + " scheduled={}, remaining={}).", + this, t, requiredSize, scheduledSize, remaining); return null; } return storage; From c61bc2d843ace614078be24b2dcbf3fb43d6f358 Mon Sep 17 00:00:00 2001 From: Wangda Tan Date: Fri, 3 Mar 2017 23:43:25 -0800 Subject: [PATCH 008/188] YARN-6278. Enforce to use correct node and npm version in new YARN-UI build. (Sunil G via wangda) --- .../hadoop-yarn/hadoop-yarn-ui/pom.xml | 68 ++++++++++--------- .../hadoop-yarn-ui/src/main/webapp/bower.json | 2 + .../src/main/webapp/package.json | 11 +-- 3 files changed, 45 insertions(+), 36 deletions(-) diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/pom.xml b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/pom.xml index 22031fa43b7..ae7ab0d3c71 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/pom.xml +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/pom.xml @@ -32,8 +32,8 @@ pom ${basedir}/target/src/main/webapp node - v0.12.2 - 2.10.0 + v4.4.5 + 2.15.5 false @@ -120,41 +120,47 @@ + + com.github.eirslett + frontend-maven-plugin + + ${webappTgtDir} + + + + generate-sources + install node and npm + + install-node-and-npm + + + ${nodeVersion} + ${npmVersion} + + + + generate-sources + npm install + + npm + + + + generate-sources + bower install + + bower + + + + + exec-maven-plugin org.codehaus.mojo - - generate-sources - npm install - - exec - - - ${webappTgtDir} - npm - - install - - - - - generate-sources - bower install - - exec - - - ${webappTgtDir} - bower - - --allow-root - install - - - ember build generate-sources diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/bower.json b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/bower.json index fe5f289ad8f..e1ab943fc18 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/bower.json +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/bower.json @@ -12,6 +12,8 @@ "loader.js": "3.3.0", "qunit": "1.19.0", "jquery-ui": "1.11.4", + "moment": "2.12.0", + "moment-timezone": "0.5.0", "more-js": "0.8.2", "bootstrap": "3.3.6", "d3": "~3.5.6", diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/package.json b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/package.json index 4cd0fdc18ad..573e5051283 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/package.json +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/package.json @@ -18,12 +18,13 @@ "author": "", "license": "Apache", "devDependencies": { + "bower": "1.7.7", "broccoli-asset-rev": "2.4.2", "broccoli-funnel": "1.0.1", - "em-table": "0.7.0", + "broccoli-merge-trees": "1.1.1", "ember-array-contains-helper": "1.0.2", "ember-bootstrap": "0.5.1", - "ember-cli": "1.13.13", + "ember-cli": "^1.13.13", "ember-cli-app-version": "1.0.0", "ember-cli-babel": "5.1.6", "ember-cli-content-security-policy": "0.4.0", @@ -33,6 +34,8 @@ "ember-cli-ic-ajax": "0.2.1", "ember-cli-inject-live-reload": "1.4.0", "ember-cli-jquery-ui": "0.0.20", + "ember-cli-moment-shim": "0.7.3", + "ember-cli-numeral": "^0.2.0", "ember-cli-qunit": "1.2.1", "ember-cli-release": "0.2.8", "ember-cli-sri": "1.2.1", @@ -48,8 +51,6 @@ }, "dependencies": { "em-helpers": "^0.8.0", - "em-table": "^0.7.0", - "ember-cli-moment-shim": "^3.0.1", - "ember-cli-numeral": "^0.2.0" + "em-table": "^0.7.0" } } From c1386a90d7ff65b57be00a7245de82c31abb4665 Mon Sep 17 00:00:00 2001 From: Sunil G Date: Sat, 4 Mar 2017 13:46:35 +0530 Subject: [PATCH 009/188] =?UTF-8?q?YARN-6248.=20user=20is=20not=20removed?= =?UTF-8?q?=20from=20UsersManager=E2=80=99s=20when=20app=20is=20killed=20w?= =?UTF-8?q?ith=20pending=20container=20requests.=20Contributed=20by=20Eric?= =?UTF-8?q?=20Payne.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resourcemanager/scheduler/capacity/UsersManager.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/UsersManager.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/UsersManager.java index 05503c6263d..c2134eb3309 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/UsersManager.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/UsersManager.java @@ -872,7 +872,9 @@ public class UsersManager implements AbstractUsersManager { this.writeLock.lock(); // For UT case: We might need to add the user to users list. - User user = getUserAndAddIfAbsent(userName); + User user = getUser(userName); + if (user == null) return; + ResourceUsage resourceUsage = user.getResourceUsage(); // If User is moved to non-active list, moved resource usage from // non-active to active list. From c8bd8ac7f9642b8db5949a914f209d5de3d4ef9b Mon Sep 17 00:00:00 2001 From: Daniel Templeton Date: Sat, 4 Mar 2017 11:00:03 -0800 Subject: [PATCH 010/188] =?UTF-8?q?HADOOP-14026.=20start-build-env.sh:=20i?= =?UTF-8?q?nvalid=20docker=20image=20name=20(Contributed=20by=20Gerg=C5=91?= =?UTF-8?q?=20P=C3=A1sztor=20via=20Daniel=20Templeton)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- start-build-env.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/start-build-env.sh b/start-build-env.sh index 1fa084f7796..18e3a8c38c4 100755 --- a/start-build-env.sh +++ b/start-build-env.sh @@ -31,7 +31,7 @@ else # boot2docker uid and gid GROUP_ID=50 fi -docker build -t "hadoop-build-${USER_NAME}" - < Date: Mon, 6 Mar 2017 10:30:01 +0900 Subject: [PATCH 011/188] HADOOP-14056. Update maven-javadoc-plugin to 2.10.4. --- hadoop-project/pom.xml | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hadoop-project/pom.xml b/hadoop-project/pom.xml index c8aa85777de..4c8b9006471 100644 --- a/hadoop-project/pom.xml +++ b/hadoop-project/pom.xml @@ -1322,7 +1322,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 2.8.1 + ${maven-javadoc-plugin.version} -Xmaxwarns 10000 diff --git a/pom.xml b/pom.xml index 3eeba1e793e..f07256da89d 100644 --- a/pom.xml +++ b/pom.xml @@ -98,7 +98,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xs 2.4 2.10 1.4.1 - 2.9.1 + 2.10.4 1.5 1.5 3.0.1 From 3536ce031ca780d6de83cf67779f571a0142ccc8 Mon Sep 17 00:00:00 2001 From: Yiqun Lin Date: Mon, 6 Mar 2017 19:04:03 +0800 Subject: [PATCH 012/188] HDFS-8741. Proper error msg to be printed when invalid operation type is given to WebHDFS operations. Contributed by Surendra Singh Lilhore. --- .../hdfs/web/resources/DeleteOpParam.java | 11 ++++- .../hadoop/hdfs/web/resources/GetOpParam.java | 11 ++++- .../hdfs/web/resources/PostOpParam.java | 11 ++++- .../hadoop/hdfs/web/resources/PutOpParam.java | 11 ++++- .../hadoop/hdfs/web/resources/TestParam.java | 41 +++++++++++++++++++ 5 files changed, 81 insertions(+), 4 deletions(-) diff --git a/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/web/resources/DeleteOpParam.java b/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/web/resources/DeleteOpParam.java index 25bed1c6d3e..e765498b5f0 100644 --- a/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/web/resources/DeleteOpParam.java +++ b/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/web/resources/DeleteOpParam.java @@ -72,7 +72,16 @@ public class DeleteOpParam extends HttpOpParam { * @param str a string representation of the parameter value. */ public DeleteOpParam(final String str) { - super(DOMAIN, DOMAIN.parse(str)); + super(DOMAIN, getOp(str)); + } + + private static Op getOp(String str) { + try { + return DOMAIN.parse(str); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException(str + " is not a valid " + Type.DELETE + + " operation."); + } } @Override diff --git a/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/web/resources/GetOpParam.java b/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/web/resources/GetOpParam.java index 1321bf6fad9..d32af330ae7 100644 --- a/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/web/resources/GetOpParam.java +++ b/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/web/resources/GetOpParam.java @@ -111,7 +111,16 @@ public class GetOpParam extends HttpOpParam { * @param str a string representation of the parameter value. */ public GetOpParam(final String str) { - super(DOMAIN, DOMAIN.parse(str)); + super(DOMAIN, getOp(str)); + } + + private static Op getOp(String str) { + try { + return DOMAIN.parse(str); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException(str + " is not a valid " + Type.GET + + " operation."); + } } @Override diff --git a/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/web/resources/PostOpParam.java b/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/web/resources/PostOpParam.java index 56a14c7cc4a..305db46e9c2 100644 --- a/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/web/resources/PostOpParam.java +++ b/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/web/resources/PostOpParam.java @@ -80,7 +80,16 @@ public class PostOpParam extends HttpOpParam { * @param str a string representation of the parameter value. */ public PostOpParam(final String str) { - super(DOMAIN, DOMAIN.parse(str)); + super(DOMAIN, getOp(str)); + } + + private static Op getOp(String str) { + try { + return DOMAIN.parse(str); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException(str + " is not a valid " + Type.POST + + " operation."); + } } @Override diff --git a/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/web/resources/PutOpParam.java b/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/web/resources/PutOpParam.java index 4bb48a62288..558bb53a429 100644 --- a/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/web/resources/PutOpParam.java +++ b/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/web/resources/PutOpParam.java @@ -107,7 +107,16 @@ public class PutOpParam extends HttpOpParam { * @param str a string representation of the parameter value. */ public PutOpParam(final String str) { - super(DOMAIN, DOMAIN.parse(str)); + super(DOMAIN, getOp(str)); + } + + private static Op getOp(String str) { + try { + return DOMAIN.parse(str); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException(str + " is not a valid " + Type.PUT + + " operation."); + } } @Override diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/web/resources/TestParam.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/web/resources/TestParam.java index d444cb4360a..6c145a4b6c0 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/web/resources/TestParam.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/web/resources/TestParam.java @@ -36,6 +36,7 @@ import org.apache.hadoop.fs.XAttrSetFlag; import org.apache.hadoop.fs.permission.AclEntry; import org.apache.hadoop.fs.permission.FsPermission; import org.apache.hadoop.hdfs.DFSConfigKeys; +import org.apache.hadoop.test.GenericTestUtils; import org.apache.hadoop.util.StringUtils; import org.junit.Assert; import org.junit.Test; @@ -503,4 +504,44 @@ public class TestParam { p = new StoragePolicyParam("COLD"); Assert.assertEquals("COLD", p.getValue()); } + + @Test + public void testHttpOpParams() { + try { + new PostOpParam("TEST"); + Assert + .fail("Construct the PostOpParam with param value 'TEST' should be" + + " failed."); + } catch (IllegalArgumentException e) { + GenericTestUtils.assertExceptionContains( + "TEST is not a valid POST operation.", e); + } + try { + new PutOpParam("TEST"); + Assert + .fail("Construct the PutOpParam with param value 'TEST' should be" + + " failed."); + } catch (IllegalArgumentException e) { + GenericTestUtils.assertExceptionContains( + "TEST is not a valid PUT operation.", e); + } + try { + new DeleteOpParam("TEST"); + Assert + .fail("Construct the DeleteOpParam with param value 'TEST' should be" + + " failed."); + } catch (IllegalArgumentException e) { + GenericTestUtils.assertExceptionContains( + "TEST is not a valid DELETE operation.", e); + } + try { + new GetOpParam("TEST"); + Assert + .fail("Construct the GetOpParam with param value 'TEST' should be" + + " failed."); + } catch (IllegalArgumentException e) { + GenericTestUtils.assertExceptionContains( + "TEST is not a valid GET operation.", e); + } + } } From 209ecd1a5c056e096a5d804759f88302608d503b Mon Sep 17 00:00:00 2001 From: Akira Ajisaka Date: Tue, 7 Mar 2017 01:32:47 +0900 Subject: [PATCH 013/188] HADOOP-14108. CLI MiniCluster: add an option to specify NameNode HTTP port. Contributed by Takanobu Asanuma. --- .../hadoop-common/src/site/markdown/CLIMiniCluster.md.vm | 1 + .../apache/hadoop/mapreduce/MiniHadoopClusterManager.java | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/hadoop-common-project/hadoop-common/src/site/markdown/CLIMiniCluster.md.vm b/hadoop-common-project/hadoop-common/src/site/markdown/CLIMiniCluster.md.vm index 74c241400fa..806df0a41dc 100644 --- a/hadoop-common-project/hadoop-common/src/site/markdown/CLIMiniCluster.md.vm +++ b/hadoop-common-project/hadoop-common/src/site/markdown/CLIMiniCluster.md.vm @@ -55,6 +55,7 @@ There are a number of command line arguments that the users can use to control w $ -namenode URL of the namenode (default is either the DFS $ cluster or a temporary dir) $ -nnport NameNode port (default 0--we choose) + $ -nnhttpport NameNode HTTP port (default 0--we choose) $ -nodemanagers How many nodemanagers to start (default 1) $ -nodfs Don't start a mini DFS cluster $ -nomr Don't start a mini MR cluster diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/java/org/apache/hadoop/mapreduce/MiniHadoopClusterManager.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/java/org/apache/hadoop/mapreduce/MiniHadoopClusterManager.java index 3cc73b5abad..324f0ca0322 100644 --- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/java/org/apache/hadoop/mapreduce/MiniHadoopClusterManager.java +++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/java/org/apache/hadoop/mapreduce/MiniHadoopClusterManager.java @@ -69,6 +69,7 @@ public class MiniHadoopClusterManager { private int numNodeManagers; private int numDataNodes; private int nnPort; + private int nnHttpPort; private int rmPort; private int jhsPort; private StartupOption dfsOpts; @@ -92,6 +93,8 @@ public class MiniHadoopClusterManager { .addOption("datanodes", true, "How many datanodes to start (default 1)") .addOption("format", false, "Format the DFS (default false)") .addOption("nnport", true, "NameNode port (default 0--we choose)") + .addOption("nnhttpport", true, + "NameNode HTTP port (default 0--we choose)") .addOption( "namenode", true, @@ -152,7 +155,8 @@ public class MiniHadoopClusterManager { URISyntaxException { if (!noDFS) { dfs = new MiniDFSCluster.Builder(conf).nameNodePort(nnPort) - .numDataNodes(numDataNodes).startupOption(dfsOpts).build(); + .nameNodeHttpPort(nnHttpPort).numDataNodes(numDataNodes) + .startupOption(dfsOpts).build(); LOG.info("Started MiniDFSCluster -- namenode on port " + dfs.getNameNodePort()); } @@ -254,6 +258,7 @@ public class MiniHadoopClusterManager { noDFS = cli.hasOption("nodfs"); numDataNodes = intArgument(cli, "datanodes", 1); nnPort = intArgument(cli, "nnport", 0); + nnHttpPort = intArgument(cli, "nnhttpport", 0); dfsOpts = cli.hasOption("format") ? StartupOption.FORMAT : StartupOption.REGULAR; From ec839b94c0eb3f09e74f8a3b0bc9a08b3f5418b2 Mon Sep 17 00:00:00 2001 From: Andrew Wang Date: Mon, 6 Mar 2017 10:47:15 -0800 Subject: [PATCH 014/188] HDFS-11441. Add escaping to error message in KMS web UI. Contributed by Aaron T. Myers. --- .../hadoop/crypto/key/kms/server/KMSAuthenticationFilter.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hadoop-common-project/hadoop-kms/src/main/java/org/apache/hadoop/crypto/key/kms/server/KMSAuthenticationFilter.java b/hadoop-common-project/hadoop-kms/src/main/java/org/apache/hadoop/crypto/key/kms/server/KMSAuthenticationFilter.java index 45e48e9a7d8..3e98a252320 100644 --- a/hadoop-common-project/hadoop-kms/src/main/java/org/apache/hadoop/crypto/key/kms/server/KMSAuthenticationFilter.java +++ b/hadoop-common-project/hadoop-kms/src/main/java/org/apache/hadoop/crypto/key/kms/server/KMSAuthenticationFilter.java @@ -20,6 +20,7 @@ package org.apache.hadoop.crypto.key.kms.server; import org.apache.hadoop.classification.InterfaceAudience; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.crypto.key.kms.KMSDelegationToken; +import org.apache.hadoop.http.HtmlQuoting; import org.apache.hadoop.security.authentication.server.KerberosAuthenticationHandler; import org.apache.hadoop.security.authentication.server.PseudoAuthenticationHandler; import org.apache.hadoop.security.token.delegation.web.DelegationTokenAuthenticationFilter; @@ -105,7 +106,7 @@ public class KMSAuthenticationFilter public void sendError(int sc, String msg) throws IOException { statusCode = sc; this.msg = msg; - super.sendError(sc, msg); + super.sendError(sc, HtmlQuoting.quoteHtmlChars(msg)); } @Override From d9dc444dc73fbe23f9e553d63baf83f12c636fa7 Mon Sep 17 00:00:00 2001 From: Ray Chiang Date: Mon, 6 Mar 2017 14:02:49 -0800 Subject: [PATCH 015/188] YARN-5665. Enhance documentation for yarn.resourcemanager.scheduler.class property. (Yufei Gu via rchiang) --- .../hadoop-common/src/site/markdown/ClusterSetup.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hadoop-common-project/hadoop-common/src/site/markdown/ClusterSetup.md b/hadoop-common-project/hadoop-common/src/site/markdown/ClusterSetup.md index 1d9e9da5783..7be6a192c9c 100644 --- a/hadoop-common-project/hadoop-common/src/site/markdown/ClusterSetup.md +++ b/hadoop-common-project/hadoop-common/src/site/markdown/ClusterSetup.md @@ -139,7 +139,7 @@ This section deals with important parameters to be specified in the given config | `yarn.resourcemanager.admin.address` | `ResourceManager` host:port for administrative commands. | *host:port* If set, overrides the hostname set in `yarn.resourcemanager.hostname`. | | `yarn.resourcemanager.webapp.address` | `ResourceManager` web-ui host:port. | *host:port* If set, overrides the hostname set in `yarn.resourcemanager.hostname`. | | `yarn.resourcemanager.hostname` | `ResourceManager` host. | *host* Single hostname that can be set in place of setting all `yarn.resourcemanager*address` resources. Results in default ports for ResourceManager components. | -| `yarn.resourcemanager.scheduler.class` | `ResourceManager` Scheduler class. | `CapacityScheduler` (recommended), `FairScheduler` (also recommended), or `FifoScheduler` | +| `yarn.resourcemanager.scheduler.class` | `ResourceManager` Scheduler class. | `CapacityScheduler` (recommended), `FairScheduler` (also recommended), or `FifoScheduler`. Use a fully qualified class name, e.g., `org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FairScheduler`. | | `yarn.scheduler.minimum-allocation-mb` | Minimum limit of memory to allocate to each container request at the `Resource Manager`. | In MBs | | `yarn.scheduler.maximum-allocation-mb` | Maximum limit of memory to allocate to each container request at the `Resource Manager`. | In MBs | | `yarn.resourcemanager.nodes.include-path` / `yarn.resourcemanager.nodes.exclude-path` | List of permitted/excluded NodeManagers. | If necessary, use these files to control the list of allowable NodeManagers. | From 5e74196ede9bfc20eb6d6fe3aa6a0e5c47a40fdd Mon Sep 17 00:00:00 2001 From: Andrew Wang Date: Mon, 6 Mar 2017 15:04:13 -0800 Subject: [PATCH 016/188] HDFS-11498. Make RestCsrfPreventionHandler and WebHdfsHandler compatible with Netty 4.0. --- .../web/RestCsrfPreventionFilterHandler.java | 6 ++-- .../datanode/web/webhdfs/WebHdfsHandler.java | 33 +++++++++---------- hadoop-project/pom.xml | 2 +- 3 files changed, 20 insertions(+), 21 deletions(-) diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/web/RestCsrfPreventionFilterHandler.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/web/RestCsrfPreventionFilterHandler.java index f2f0533894d..4958bb59202 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/web/RestCsrfPreventionFilterHandler.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/web/RestCsrfPreventionFilterHandler.java @@ -17,8 +17,8 @@ */ package org.apache.hadoop.hdfs.server.datanode.web; -import static io.netty.handler.codec.http.HttpHeaderNames.CONNECTION; -import static io.netty.handler.codec.http.HttpHeaderValues.CLOSE; +import static io.netty.handler.codec.http.HttpHeaders.Names.CONNECTION; +import static io.netty.handler.codec.http.HttpHeaders.Values.CLOSE; import static io.netty.handler.codec.http.HttpResponseStatus.INTERNAL_SERVER_ERROR; import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; @@ -119,7 +119,7 @@ final class RestCsrfPreventionFilterHandler @Override public String getMethod() { - return req.method().name(); + return req.getMethod().name(); } @Override diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/web/webhdfs/WebHdfsHandler.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/web/webhdfs/WebHdfsHandler.java index d2b2ec22346..c5fc7ea1709 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/web/webhdfs/WebHdfsHandler.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/web/webhdfs/WebHdfsHandler.java @@ -29,17 +29,6 @@ import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.QueryStringDecoder; import io.netty.handler.stream.ChunkedStream; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.InetSocketAddress; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.charset.StandardCharsets; -import java.security.PrivilegedExceptionAction; -import java.util.EnumSet; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; @@ -63,17 +52,27 @@ import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.token.Token; import org.apache.hadoop.util.LimitInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.security.PrivilegedExceptionAction; +import java.util.EnumSet; + +import static io.netty.handler.codec.http.HttpHeaders.Names.ACCEPT; +import static io.netty.handler.codec.http.HttpHeaders.Names.ACCESS_CONTROL_ALLOW_HEADERS; import static io.netty.handler.codec.http.HttpHeaders.Names.ACCESS_CONTROL_ALLOW_METHODS; import static io.netty.handler.codec.http.HttpHeaders.Names.ACCESS_CONTROL_ALLOW_ORIGIN; +import static io.netty.handler.codec.http.HttpHeaders.Names.ACCESS_CONTROL_MAX_AGE; import static io.netty.handler.codec.http.HttpHeaders.Names.CONNECTION; import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_LENGTH; import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE; import static io.netty.handler.codec.http.HttpHeaders.Names.LOCATION; import static io.netty.handler.codec.http.HttpHeaders.Values.CLOSE; -import static io.netty.handler.codec.http.HttpHeaderNames.ACCEPT; -import static io.netty.handler.codec.http.HttpHeaderNames.ACCESS_CONTROL_ALLOW_HEADERS; -import static io.netty.handler.codec.http.HttpHeaderNames.ACCESS_CONTROL_MAX_AGE; -import static io.netty.handler.codec.http.HttpHeaderValues.KEEP_ALIVE; +import static io.netty.handler.codec.http.HttpHeaders.Values.KEEP_ALIVE; import static io.netty.handler.codec.http.HttpMethod.GET; import static io.netty.handler.codec.http.HttpMethod.OPTIONS; import static io.netty.handler.codec.http.HttpMethod.POST; @@ -145,7 +144,7 @@ public class WebHdfsHandler extends SimpleChannelInboundHandler { LOG.warn("Error retrieving hostname: ", e); host = "unknown"; } - REQLOG.info(host + " " + req.method() + " " + req.uri() + " " + + REQLOG.info(host + " " + req.getMethod() + " " + req.getUri() + " " + getResponseCode()); } return null; @@ -155,7 +154,7 @@ public class WebHdfsHandler extends SimpleChannelInboundHandler { int getResponseCode() { return (resp == null) ? INTERNAL_SERVER_ERROR.code() : - resp.status().code(); + resp.getStatus().code(); } public void handle(ChannelHandlerContext ctx, HttpRequest req) diff --git a/hadoop-project/pom.xml b/hadoop-project/pom.xml index 4c8b9006471..572f0e6eb29 100644 --- a/hadoop-project/pom.xml +++ b/hadoop-project/pom.xml @@ -719,7 +719,7 @@ io.netty netty-all - 4.1.0.Beta5 + 4.0.23.Final From b5adc5c3011f111f86d232cb33ec522547f68a95 Mon Sep 17 00:00:00 2001 From: Arpit Agarwal Date: Mon, 6 Mar 2017 16:39:53 -0800 Subject: [PATCH 017/188] HDFS-10838. Last full block report received time for each DN should be easily discoverable. Contributed by Surendra Singh Lilhore. --- .../hadoop/hdfs/protocol/DatanodeInfo.java | 52 ++++++++++++++++++- .../hdfs/protocolPB/PBHelperClient.java | 8 ++- .../hadoop/hdfs/web/JsonUtilClient.java | 2 + .../src/main/proto/hdfs.proto | 2 + .../server/blockmanagement/BlockManager.java | 3 ++ .../hdfs/server/namenode/FSNamesystem.java | 8 ++- .../org/apache/hadoop/hdfs/web/JsonUtil.java | 3 ++ .../src/main/webapps/hdfs/dfshealth.html | 2 + .../src/main/webapps/hdfs/dfshealth.js | 1 + .../server/namenode/TestNameNodeMXBean.java | 1 + 10 files changed, 78 insertions(+), 4 deletions(-) diff --git a/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/protocol/DatanodeInfo.java b/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/protocol/DatanodeInfo.java index acbcffa6f03..e1698c98c03 100644 --- a/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/protocol/DatanodeInfo.java +++ b/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/protocol/DatanodeInfo.java @@ -85,6 +85,8 @@ public class DatanodeInfo extends DatanodeID implements Node { protected AdminStates adminState; private long maintenanceExpireTimeInMS; + private long lastBlockReportTime; + private long lastBlockReportMonotonic; protected DatanodeInfo(DatanodeInfo from) { super(from); @@ -101,6 +103,8 @@ public class DatanodeInfo extends DatanodeID implements Node { this.location = from.getNetworkLocation(); this.adminState = from.getAdminState(); this.upgradeDomain = from.getUpgradeDomain(); + this.lastBlockReportTime = from.getLastBlockReportTime(); + this.lastBlockReportMonotonic = from.getLastBlockReportMonotonic(); } protected DatanodeInfo(DatanodeID nodeID) { @@ -116,6 +120,8 @@ public class DatanodeInfo extends DatanodeID implements Node { this.lastUpdateMonotonic = 0L; this.xceiverCount = 0; this.adminState = null; + this.lastBlockReportTime = 0L; + this.lastBlockReportMonotonic = 0L; } protected DatanodeInfo(DatanodeID nodeID, String location) { @@ -131,7 +137,8 @@ public class DatanodeInfo extends DatanodeID implements Node { final long blockPoolUsed, final long cacheCapacity, final long cacheUsed, final long lastUpdate, final long lastUpdateMonotonic, final int xceiverCount, final String networkLocation, - final AdminStates adminState, final String upgradeDomain) { + final AdminStates adminState, final String upgradeDomain, + final long lastBlockReportTime, final long lastBlockReportMonotonic) { super(ipAddr, hostName, datanodeUuid, xferPort, infoPort, infoSecurePort, ipcPort); this.capacity = capacity; @@ -147,6 +154,8 @@ public class DatanodeInfo extends DatanodeID implements Node { this.location = networkLocation; this.adminState = adminState; this.upgradeDomain = upgradeDomain; + this.lastBlockReportTime = lastBlockReportTime; + this.lastBlockReportMonotonic = lastBlockReportMonotonic; } /** Network location name. */ @@ -391,6 +400,11 @@ public class DatanodeInfo extends DatanodeID implements Node { .append(percent2String(cacheRemainingPercent)).append("\n"); buffer.append("Xceivers: ").append(getXceiverCount()).append("\n"); buffer.append("Last contact: ").append(new Date(lastUpdate)).append("\n"); + buffer + .append("Last Block Report: ") + .append( + lastBlockReportTime != 0 ? new Date(lastBlockReportTime) : "Never") + .append("\n"); return buffer.toString(); } @@ -503,6 +517,26 @@ public class DatanodeInfo extends DatanodeID implements Node { return this.maintenanceExpireTimeInMS; } + /** Sets the last block report time. */ + public void setLastBlockReportTime(long lastBlockReportTime) { + this.lastBlockReportTime = lastBlockReportTime; + } + + /** Sets the last block report monotonic time. */ + public void setLastBlockReportMonotonic(long lastBlockReportMonotonic) { + this.lastBlockReportMonotonic = lastBlockReportMonotonic; + } + + /** Last block report time. */ + public long getLastBlockReportTime() { + return lastBlockReportTime; + } + + /** Last block report monotonic time. */ + public long getLastBlockReportMonotonic() { + return lastBlockReportMonotonic; + } + /** * Take the node out of maintenance mode. */ @@ -643,6 +677,8 @@ public class DatanodeInfo extends DatanodeID implements Node { private int infoSecurePort; private int ipcPort; private long nonDfsUsed = 0L; + private long lastBlockReportTime = 0L; + private long lastBlockReportMonotonic = 0L; public DatanodeInfoBuilder setFrom(DatanodeInfo from) { this.capacity = from.getCapacity(); @@ -658,6 +694,8 @@ public class DatanodeInfo extends DatanodeID implements Node { this.location = from.getNetworkLocation(); this.adminState = from.getAdminState(); this.upgradeDomain = from.getUpgradeDomain(); + this.lastBlockReportTime = from.getLastBlockReportTime(); + this.lastBlockReportMonotonic = from.getLastBlockReportMonotonic(); setNodeID(from); return this; } @@ -775,12 +813,22 @@ public class DatanodeInfo extends DatanodeID implements Node { return this; } + public DatanodeInfoBuilder setLastBlockReportTime(long time) { + this.lastBlockReportTime = time; + return this; + } + + public DatanodeInfoBuilder setLastBlockReportMonotonic(long time) { + this.lastBlockReportMonotonic = time; + return this; + } + public DatanodeInfo build() { return new DatanodeInfo(ipAddr, hostName, datanodeUuid, xferPort, infoPort, infoSecurePort, ipcPort, capacity, dfsUsed, nonDfsUsed, remaining, blockPoolUsed, cacheCapacity, cacheUsed, lastUpdate, lastUpdateMonotonic, xceiverCount, location, adminState, - upgradeDomain); + upgradeDomain, lastBlockReportTime, lastBlockReportMonotonic); } } } diff --git a/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/protocolPB/PBHelperClient.java b/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/protocolPB/PBHelperClient.java index ad80bc255a6..b4fa9267fc2 100644 --- a/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/protocolPB/PBHelperClient.java +++ b/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/protocolPB/PBHelperClient.java @@ -303,6 +303,8 @@ public class PBHelperClient { .setLastUpdateMonotonic(info.getLastUpdateMonotonic()) .setXceiverCount(info.getXceiverCount()) .setAdminState(convert(info.getAdminState())) + .setLastBlockReportTime(info.getLastBlockReportTime()) + .setLastBlockReportMonotonic(info.getLastBlockReportMonotonic()) .build(); return builder.build(); } @@ -650,7 +652,11 @@ public class PBHelperClient { .setLastUpdateMonotonic(di.getLastUpdateMonotonic()) .setXceiverCount(di.getXceiverCount()) .setAdminState(convert(di.getAdminState())).setUpgradeDomain( - di.hasUpgradeDomain() ? di.getUpgradeDomain() : null); + di.hasUpgradeDomain() ? di.getUpgradeDomain() : null) + .setLastBlockReportTime(di.hasLastBlockReportTime() ? + di.getLastBlockReportTime() : 0) + .setLastBlockReportMonotonic(di.hasLastBlockReportMonotonic() ? + di.getLastBlockReportMonotonic() : 0); if (di.hasNonDfsUsed()) { dinfo.setNonDfsUsed(di.getNonDfsUsed()); } else { diff --git a/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/web/JsonUtilClient.java b/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/web/JsonUtilClient.java index 4204c54c816..cfcb4c9b07b 100644 --- a/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/web/JsonUtilClient.java +++ b/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/web/JsonUtilClient.java @@ -295,6 +295,8 @@ class JsonUtilClient { DatanodeInfo.AdminStates .valueOf(getString(m, "adminState", "NORMAL"))) .setUpgradeDomain(getString(m, "upgradeDomain", "")) + .setLastBlockReportTime(getLong(m, "lastBlockReportTime", 0L)) + .setLastBlockReportMonotonic(getLong(m, "lastBlockReportMonotonic", 0L)) .build(); } diff --git a/hadoop-hdfs-project/hadoop-hdfs-client/src/main/proto/hdfs.proto b/hadoop-hdfs-project/hadoop-hdfs-client/src/main/proto/hdfs.proto index 8a039d482f4..7824fd11c7a 100644 --- a/hadoop-hdfs-project/hadoop-hdfs-client/src/main/proto/hdfs.proto +++ b/hadoop-hdfs-project/hadoop-hdfs-client/src/main/proto/hdfs.proto @@ -102,6 +102,8 @@ message DatanodeInfoProto { optional uint64 cacheUsed = 12 [default = 0]; optional uint64 lastUpdateMonotonic = 13 [default = 0]; optional string upgradeDomain = 14; + optional uint64 lastBlockReportTime = 15 [default = 0]; + optional uint64 lastBlockReportMonotonic = 16 [default = 0]; } /** diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/blockmanagement/BlockManager.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/blockmanagement/BlockManager.java index 5ca0fa7e791..9ec28f9c02f 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/blockmanagement/BlockManager.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/blockmanagement/BlockManager.java @@ -20,6 +20,7 @@ package org.apache.hadoop.hdfs.server.blockmanagement; import static org.apache.hadoop.hdfs.protocol.BlockType.CONTIGUOUS; import static org.apache.hadoop.hdfs.protocol.BlockType.STRIPED; import static org.apache.hadoop.util.ExitUtil.terminate; +import static org.apache.hadoop.util.Time.now; import java.io.IOException; import java.io.PrintWriter; @@ -2377,6 +2378,8 @@ public class BlockManager implements BlockStatsMXBean { long leaseId = this.getBlockReportLeaseManager().removeLease(node); BlockManagerFaultInjector.getInstance(). removeBlockReportLease(node, leaseId); + node.setLastBlockReportTime(now()); + node.setLastBlockReportMonotonic(Time.monotonicNow()); } LOG.debug("Processing RPC with index {} out of total {} RPCs in " + "processReport 0x{}", context.getCurRpc(), 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 81c5759b573..03277af446a 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 @@ -5472,7 +5472,9 @@ public class FSNamesystem implements Namesystem, FSNamesystemMBean, .put("blockScheduled", node.getBlocksScheduled()) .put("blockPoolUsed", node.getBlockPoolUsed()) .put("blockPoolUsedPercent", node.getBlockPoolUsedPercent()) - .put("volfails", node.getVolumeFailures()); + .put("volfails", node.getVolumeFailures()) + // Block report time in minutes + .put("lastBlockReport", getLastBlockReport(node)); VolumeFailureSummary volumeFailureSummary = node.getVolumeFailureSummary(); if (volumeFailureSummary != null) { innerinfo @@ -5571,6 +5573,10 @@ public class FSNamesystem implements Namesystem, FSNamesystemMBean, return (monotonicNow() - alivenode.getLastUpdateMonotonic())/1000; } + private Object getLastBlockReport(DatanodeDescriptor node) { + return (monotonicNow() - node.getLastBlockReportMonotonic()) / 60000; + } + private long getDfsUsed(DatanodeDescriptor alivenode) { return alivenode.getDfsUsed(); } diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/web/JsonUtil.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/web/JsonUtil.java index 0d891139712..827e1a320ff 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/web/JsonUtil.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/web/JsonUtil.java @@ -181,6 +181,9 @@ public class JsonUtil { if (datanodeinfo.getUpgradeDomain() != null) { m.put("upgradeDomain", datanodeinfo.getUpgradeDomain()); } + m.put("lastBlockReportTime", datanodeinfo.getLastBlockReportTime()); + m.put("lastBlockReportMonotonic", + datanodeinfo.getLastBlockReportMonotonic()); return m; } diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/webapps/hdfs/dfshealth.html b/hadoop-hdfs-project/hadoop-hdfs/src/main/webapps/hdfs/dfshealth.html index b33b9a27e60..c01f36cdb6b 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/webapps/hdfs/dfshealth.html +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/webapps/hdfs/dfshealth.html @@ -309,6 +309,7 @@ Node Http Address Last contact + Last Block Report Capacity Blocks Block pool used @@ -320,6 +321,7 @@ {name} ({xferaddr}) {dnWebAddress} {lastContact}s + {lastBlockReport}m
{capacity|fmt_bytes}
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/webapps/hdfs/dfshealth.js b/hadoop-hdfs-project/hadoop-hdfs/src/main/webapps/hdfs/dfshealth.js index 88096d33f50..59958a1ed1e 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/webapps/hdfs/dfshealth.js +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/webapps/hdfs/dfshealth.js @@ -333,6 +333,7 @@ { 'orderDataType': 'ng-value', 'searchable': true }, { 'orderDataType': 'ng-value', 'type': 'numeric' }, { 'orderDataType': 'ng-value', 'type': 'numeric' }, + { 'orderDataType': 'ng-value', 'type': 'numeric' }, { 'orderData': 3, 'type': 'numeric' }, { 'orderDataType': 'ng-value', 'type': 'numeric'}, { 'orderData': 5 } diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestNameNodeMXBean.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestNameNodeMXBean.java index 037fd4025bd..d4d47637310 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestNameNodeMXBean.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestNameNodeMXBean.java @@ -174,6 +174,7 @@ public class TestNameNodeMXBean { assertTrue(((Long)liveNode.get("capacity")) > 0); assertTrue(liveNode.containsKey("numBlocks")); assertTrue(((Long)liveNode.get("numBlocks")) == 0); + assertTrue(liveNode.containsKey("lastBlockReport")); // a. By default the upgrade domain isn't defined on any DN. // b. If the upgrade domain is set on a DN, JMX should have the same // value. From c571cda5c7d929477961dfff4176d7de4944d874 Mon Sep 17 00:00:00 2001 From: Mingliang Liu Date: Mon, 6 Mar 2017 16:53:30 -0800 Subject: [PATCH 018/188] HADOOP-14048. REDO operation of WASB#AtomicRename should create placeholder blob for destination folder. Contributed by NITIN VERMA --- .../apache/hadoop/fs/azure/NativeAzureFileSystem.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/NativeAzureFileSystem.java b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/NativeAzureFileSystem.java index 0dfefaf196c..b1956a78541 100644 --- a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/NativeAzureFileSystem.java +++ b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/NativeAzureFileSystem.java @@ -566,6 +566,16 @@ public class NativeAzureFileSystem extends FileSystem { // Remove the source folder. Don't check explicitly if it exists, // to avoid triggering redo recursively. try { + // Rename the source folder 0-byte root file + // as destination folder 0-byte root file. + FileMetadata srcMetaData = this.getSourceMetadata(); + if (srcMetaData.getBlobMaterialization() == BlobMaterialization.Explicit) { + // We already have a lease. So let's just rename the source blob + // as destination blob under same lease. + fs.getStoreInterface().rename(this.getSrcKey(), this.getDstKey(), false, lease); + } + + // Now we can safely delete the source folder. fs.getStoreInterface().delete(srcKey, lease); } catch (Exception e) { LOG.info("Unable to delete source folder during folder rename redo. " From 52d7d5aa1a303cf70519a61487641211f4267c6f Mon Sep 17 00:00:00 2001 From: Mingliang Liu Date: Mon, 6 Mar 2017 17:10:11 -0800 Subject: [PATCH 019/188] Revert "HADOOP-13930. Azure: Add Authorization support to WASB. Contributed by Sivaguru Sankaridurg and Dushyanth" This reverts commit 6b7cd62b8cf12616b13142f2eb2cfc2f25796f0f. --- .../src/main/resources/core-default.xml | 10 - .../conf/TestCommonConfigurationFields.java | 2 - .../fs/azure/AzureNativeFileSystemStore.java | 4 +- .../fs/azure/NativeAzureFileSystem.java | 155 +--------- .../fs/azure/RemoteSASKeyGeneratorImpl.java | 179 ++++------- .../fs/azure/RemoteWasbAuthorizerImpl.java | 247 ---------------- .../fs/azure/SecureStorageInterfaceImpl.java | 6 +- .../fs/azure/WasbAuthorizationException.java | 40 --- .../fs/azure/WasbAuthorizationOperations.java | 44 --- .../fs/azure/WasbAuthorizerInterface.java | 47 --- .../hadoop/fs/azure/security/Constants.java | 54 ---- .../WasbDelegationTokenIdentifier.java | 48 --- .../fs/azure/security/WasbTokenRenewer.java | 124 -------- .../hadoop/fs/azure/security/package.html | 28 -- ...ache.hadoop.security.token.TokenIdentifier | 16 - ....apache.hadoop.security.token.TokenRenewer | 16 - .../hadoop-azure/src/site/markdown/index.md | 34 --- .../fs/azure/MockWasbAuthorizerImpl.java | 102 ------- ...estNativeAzureFileSystemAuthorization.java | 277 ------------------ 19 files changed, 62 insertions(+), 1371 deletions(-) delete mode 100644 hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/RemoteWasbAuthorizerImpl.java delete mode 100644 hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/WasbAuthorizationException.java delete mode 100644 hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/WasbAuthorizationOperations.java delete mode 100644 hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/WasbAuthorizerInterface.java delete mode 100644 hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/security/Constants.java delete mode 100644 hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/security/WasbDelegationTokenIdentifier.java delete mode 100644 hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/security/WasbTokenRenewer.java delete mode 100644 hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/security/package.html delete mode 100644 hadoop-tools/hadoop-azure/src/main/resources/META-INF/services/org.apache.hadoop.security.token.TokenIdentifier delete mode 100644 hadoop-tools/hadoop-azure/src/main/resources/META-INF/services/org.apache.hadoop.security.token.TokenRenewer delete mode 100644 hadoop-tools/hadoop-azure/src/test/java/org/apache/hadoop/fs/azure/MockWasbAuthorizerImpl.java delete mode 100644 hadoop-tools/hadoop-azure/src/test/java/org/apache/hadoop/fs/azure/TestNativeAzureFileSystemAuthorization.java diff --git a/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml b/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml index 52b58eddb1e..35be56bf81e 100644 --- a/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml +++ b/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml @@ -1292,16 +1292,6 @@ to specify the time (such as 2s, 2m, 1h, etc.). - - fs.azure.authorization - false - - Config flag to enable authorization support in WASB. Setting it to "true" enables - authorization support to WASB. Currently WASB authorization requires a remote service - to provide authorization that needs to be specified via fs.azure.authorization.remote.service.url - configuration - - diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/conf/TestCommonConfigurationFields.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/conf/TestCommonConfigurationFields.java index 7410d297a09..966a8ac0d08 100644 --- a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/conf/TestCommonConfigurationFields.java +++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/conf/TestCommonConfigurationFields.java @@ -181,8 +181,6 @@ public class TestCommonConfigurationFields extends TestConfigurationFieldsBase { xmlPropsToSkipCompare.add("io.compression.codec.bzip2.library"); // - org.apache.hadoop.io.SequenceFile xmlPropsToSkipCompare.add("io.seqfile.local.dir"); - // - org.apache.hadoop.fs.azure.NativeAzureFileSystem - xmlPropsToSkipCompare.add("fs.azure.authorization"); } diff --git a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/AzureNativeFileSystemStore.java b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/AzureNativeFileSystemStore.java index 9d7ac80cdfd..07c389ce0c2 100644 --- a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/AzureNativeFileSystemStore.java +++ b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/AzureNativeFileSystemStore.java @@ -303,7 +303,7 @@ public class AzureNativeFileSystemStore implements NativeFileSystemStore { private boolean useSecureMode = false; private boolean useLocalSasKeyMode = false; - + private String delegationToken; /** * A test hook interface that can modify the operation context we use for * Azure Storage operations, e.g. to inject errors. @@ -478,7 +478,7 @@ public class AzureNativeFileSystemStore implements NativeFileSystemStore { this.storageInteractionLayer = new StorageInterfaceImpl(); } else { this.storageInteractionLayer = new SecureStorageInterfaceImpl( - useLocalSasKeyMode, conf); + useLocalSasKeyMode, conf, delegationToken); } } diff --git a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/NativeAzureFileSystem.java b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/NativeAzureFileSystem.java index b1956a78541..4184a539418 100644 --- a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/NativeAzureFileSystem.java +++ b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/NativeAzureFileSystem.java @@ -25,12 +25,9 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.net.InetAddress; import java.net.URI; import java.net.URISyntaxException; -import java.net.URL; import java.nio.charset.Charset; -import java.security.PrivilegedExceptionAction; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; @@ -64,15 +61,10 @@ import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.fs.azure.metrics.AzureFileSystemInstrumentation; import org.apache.hadoop.fs.azure.metrics.AzureFileSystemMetricsSystem; -import org.apache.hadoop.fs.azure.security.Constants; import org.apache.hadoop.fs.permission.FsPermission; import org.apache.hadoop.fs.permission.PermissionStatus; import org.apache.hadoop.io.IOUtils; import org.apache.hadoop.security.UserGroupInformation; -import org.apache.hadoop.security.token.Token; -import org.apache.hadoop.security.token.delegation.web.DelegationTokenAuthenticatedURL; -import org.apache.hadoop.security.token.delegation.web.DelegationTokenAuthenticator; -import org.apache.hadoop.security.token.delegation.web.KerberosDelegationTokenAuthenticator; import org.apache.hadoop.util.Progressable; import org.apache.hadoop.util.Time; import org.slf4j.Logger; @@ -1114,39 +1106,7 @@ public class NativeAzureFileSystem extends FileSystem { // A counter to create unique (within-process) names for my metrics sources. private static AtomicInteger metricsSourceNameCounter = new AtomicInteger(); private boolean appendSupportEnabled = false; - private DelegationTokenAuthenticatedURL authURL; - private DelegationTokenAuthenticatedURL.Token authToken = new DelegationTokenAuthenticatedURL.Token(); - private String credServiceUrl; - - /** - * Configuration key to enable authorization support in WASB. - */ - public static final String KEY_AZURE_AUTHORIZATION = - "fs.azure.authorization"; - - /** - * Default value for the authorization support in WASB. - */ - private static final boolean DEFAULT_AZURE_AUTHORIZATION = false; - - /** - * Flag controlling authorization support in WASB. - */ - private boolean azureAuthorization = false; - - /** - * Flag controlling Kerberos support in WASB. - */ - private boolean kerberosSupportEnabled = false; - - /** - * Authorizer to use when authorization support is enabled in - * WASB. - */ - private WasbAuthorizerInterface authorizer = null; - - private String delegationToken = null; - + public NativeAzureFileSystem() { // set store in initialize() } @@ -1277,31 +1237,6 @@ public class NativeAzureFileSystem extends FileSystem { // Initialize thread counts from user configuration deleteThreadCount = conf.getInt(AZURE_DELETE_THREADS, DEFAULT_AZURE_DELETE_THREADS); renameThreadCount = conf.getInt(AZURE_RENAME_THREADS, DEFAULT_AZURE_RENAME_THREADS); - - this.azureAuthorization = conf.getBoolean(KEY_AZURE_AUTHORIZATION, - DEFAULT_AZURE_AUTHORIZATION); - this.kerberosSupportEnabled = conf.getBoolean( - Constants.AZURE_KERBEROS_SUPPORT_PROPERTY_NAME, false); - - if (this.azureAuthorization) { - - this.authorizer = - new RemoteWasbAuthorizerImpl(); - authorizer.init(conf); - } - if (UserGroupInformation.isSecurityEnabled() && kerberosSupportEnabled) { - DelegationTokenAuthenticator authenticator = new KerberosDelegationTokenAuthenticator(); - authURL = new DelegationTokenAuthenticatedURL(authenticator); - credServiceUrl = conf.get(Constants.KEY_CRED_SERVICE_URL, String - .format("http://%s:%s", - InetAddress.getLocalHost().getCanonicalHostName(), - Constants.DEFAULT_CRED_SERVICE_PORT)); - } - } - - @VisibleForTesting - public void updateWasbAuthorizer(WasbAuthorizerInterface authorizer) { - this.authorizer = authorizer; } private NativeFileSystemStore createDefaultStore(Configuration conf) { @@ -1415,15 +1350,6 @@ public class NativeAzureFileSystem extends FileSystem { return store; } - private void performAuthCheck(String path, String accessType, - String operation) throws WasbAuthorizationException, IOException { - - if (azureAuthorization && !this.authorizer.authorize(path, accessType)) { - throw new WasbAuthorizationException(operation - + " operation for Path : " + path + " not allowed"); - } - } - /** * Gets the metrics source for this file system. * This is mainly here for unit testing purposes. @@ -1446,10 +1372,6 @@ public class NativeAzureFileSystem extends FileSystem { LOG.debug("Opening file: {} for append", f); Path absolutePath = makeAbsolute(f); - - performAuthCheck(absolutePath.toString(), - WasbAuthorizationOperations.WRITE.toString(), "append"); - String key = pathToKey(absolutePath); FileMetadata meta = null; try { @@ -1650,10 +1572,6 @@ public class NativeAzureFileSystem extends FileSystem { } Path absolutePath = makeAbsolute(f); - - performAuthCheck(absolutePath.toString(), - WasbAuthorizationOperations.WRITE.toString(), "create"); - String key = pathToKey(absolutePath); FileMetadata existingMetadata = store.retrieveMetadata(key); @@ -1776,10 +1694,6 @@ public class NativeAzureFileSystem extends FileSystem { LOG.debug("Deleting file: {}", f.toString()); Path absolutePath = makeAbsolute(f); - - performAuthCheck(absolutePath.toString(), - WasbAuthorizationOperations.EXECUTE.toString(), "delete"); - String key = pathToKey(absolutePath); // Capture the metadata for the path. @@ -2050,10 +1964,6 @@ public class NativeAzureFileSystem extends FileSystem { // Capture the absolute path and the path to key. Path absolutePath = makeAbsolute(f); - - performAuthCheck(absolutePath.toString(), - WasbAuthorizationOperations.EXECUTE.toString(), "getFileStatus"); - String key = pathToKey(absolutePath); if (key.length() == 0) { // root always exists return newDirectory(null, absolutePath); @@ -2152,10 +2062,6 @@ public class NativeAzureFileSystem extends FileSystem { LOG.debug("Listing status for {}", f.toString()); Path absolutePath = makeAbsolute(f); - - performAuthCheck(absolutePath.toString(), - WasbAuthorizationOperations.EXECUTE.toString(), "list"); - String key = pathToKey(absolutePath); Set status = new TreeSet(); FileMetadata meta = null; @@ -2378,10 +2284,6 @@ public class NativeAzureFileSystem extends FileSystem { } Path absolutePath = makeAbsolute(f); - - performAuthCheck(absolutePath.toString(), - WasbAuthorizationOperations.EXECUTE.toString(), "mkdirs"); - PermissionStatus permissionStatus = null; if(noUmask) { // ensure owner still has wx permissions at the minimum @@ -2435,10 +2337,6 @@ public class NativeAzureFileSystem extends FileSystem { LOG.debug("Opening file: {}", f.toString()); Path absolutePath = makeAbsolute(f); - - performAuthCheck(absolutePath.toString(), - WasbAuthorizationOperations.READ.toString(), "read"); - String key = pathToKey(absolutePath); FileMetadata meta = null; try { @@ -2495,12 +2393,7 @@ public class NativeAzureFileSystem extends FileSystem { + " through WASB that has colons in the name"); } - Path absolutePath = makeAbsolute(src); - - performAuthCheck(absolutePath.toString(), - WasbAuthorizationOperations.EXECUTE.toString(), "rename"); - - String srcKey = pathToKey(absolutePath); + String srcKey = pathToKey(makeAbsolute(src)); if (srcKey.length() == 0) { // Cannot rename root of file system @@ -2802,10 +2695,6 @@ public class NativeAzureFileSystem extends FileSystem { @Override public void setPermission(Path p, FsPermission permission) throws FileNotFoundException, IOException { Path absolutePath = makeAbsolute(p); - - performAuthCheck(absolutePath.toString(), - WasbAuthorizationOperations.EXECUTE.toString(), "setPermission"); - String key = pathToKey(absolutePath); FileMetadata metadata = null; try { @@ -2844,10 +2733,6 @@ public class NativeAzureFileSystem extends FileSystem { public void setOwner(Path p, String username, String groupname) throws IOException { Path absolutePath = makeAbsolute(p); - - performAuthCheck(absolutePath.toString(), - WasbAuthorizationOperations.EXECUTE.toString(), "setOwner"); - String key = pathToKey(absolutePath); FileMetadata metadata = null; @@ -2910,42 +2795,6 @@ public class NativeAzureFileSystem extends FileSystem { isClosed = true; } - @Override - public Token getDelegationToken(final String renewer) throws IOException { - if(kerberosSupportEnabled) { - try { - final UserGroupInformation ugi = UserGroupInformation.getCurrentUser(); - UserGroupInformation connectUgi = ugi.getRealUser(); - final UserGroupInformation proxyUser = connectUgi; - if (connectUgi == null) { - connectUgi = ugi; - } - if(!connectUgi.hasKerberosCredentials()){ - connectUgi = UserGroupInformation.getLoginUser(); - } - connectUgi.checkTGTAndReloginFromKeytab(); - return connectUgi.doAs(new PrivilegedExceptionAction>() { - @Override - public Token run() throws Exception { - return authURL.getDelegationToken(new URL(credServiceUrl - + Constants.DEFAULT_DELEGATION_TOKEN_MANAGER_ENDPOINT), - authToken, renewer, (proxyUser != null)? ugi.getShortUserName(): null); - } - }); - } catch (Exception ex) { - LOG.error("Error in fetching the delegation token from remote service", - ex); - if (ex instanceof IOException) { - throw (IOException) ex; - } else { - throw new IOException(ex); - } - } - } else { - return super.getDelegationToken(renewer); - } - } - /** * A handler that defines what to do with blobs whose upload was * interrupted. diff --git a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/RemoteSASKeyGeneratorImpl.java b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/RemoteSASKeyGeneratorImpl.java index 43672b27a66..404419d7422 100644 --- a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/RemoteSASKeyGeneratorImpl.java +++ b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/RemoteSASKeyGeneratorImpl.java @@ -19,23 +19,10 @@ package org.apache.hadoop.fs.azure; import java.io.IOException; -import java.net.InetAddress; import java.net.URI; import java.net.URISyntaxException; -import java.net.UnknownHostException; -import java.security.PrivilegedExceptionAction; -import java.util.Iterator; -import org.apache.commons.lang.Validate; import org.apache.hadoop.conf.Configuration; -import org.apache.hadoop.fs.azure.security.Constants; -import org.apache.hadoop.fs.azure.security.WasbDelegationTokenIdentifier; -import org.apache.hadoop.security.UserGroupInformation; -import org.apache.hadoop.security.authentication.client.AuthenticatedURL; -import org.apache.hadoop.security.authentication.client.Authenticator; -import org.apache.hadoop.security.token.Token; -import org.apache.hadoop.security.token.TokenIdentifier; -import org.apache.hadoop.security.token.delegation.web.KerberosDelegationTokenAuthenticator; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.utils.URIBuilder; import org.slf4j.Logger; @@ -56,6 +43,12 @@ public class RemoteSASKeyGeneratorImpl extends SASKeyGeneratorImpl { public static final Logger LOG = LoggerFactory.getLogger(AzureNativeFileSystemStore.class); + /** + * Configuration parameter name expected in the Configuration + * object to provide the url of the remote service {@value} + */ + private static final String KEY_CRED_SERVICE_URL = + "fs.azure.cred.service.url"; /** * Container SAS Key generation OP name. {@value} @@ -89,7 +82,7 @@ public class RemoteSASKeyGeneratorImpl extends SASKeyGeneratorImpl { * Query parameter name for user info {@value} */ private static final String DELEGATION_TOKEN_QUERY_PARAM_NAME = - "delegation"; + "delegation_token"; /** * Query parameter name for the relative path inside the storage @@ -101,40 +94,24 @@ public class RemoteSASKeyGeneratorImpl extends SASKeyGeneratorImpl { private String delegationToken = ""; private String credServiceUrl = ""; private WasbRemoteCallHelper remoteCallHelper = null; - private boolean isSecurityEnabled; - private boolean isKerberosSupportEnabled; public RemoteSASKeyGeneratorImpl(Configuration conf) { super(conf); } - public boolean initialize(Configuration conf) { + public boolean initialize(Configuration conf, String delegationToken) { LOG.debug("Initializing RemoteSASKeyGeneratorImpl instance"); - Iterator> tokenIterator = null; - try { - tokenIterator = UserGroupInformation.getCurrentUser().getCredentials() - .getAllTokens().iterator(); - while (tokenIterator.hasNext()) { - Token iteratedToken = tokenIterator.next(); - if (iteratedToken.getKind().equals(WasbDelegationTokenIdentifier.TOKEN_KIND)) { - delegationToken = iteratedToken.encodeToUrlString(); - } - } - } catch (IOException e) { - LOG.error("Error in fetching the WASB delegation token"); - } + credServiceUrl = conf.get(KEY_CRED_SERVICE_URL); - try { - credServiceUrl = conf.get(Constants.KEY_CRED_SERVICE_URL, String - .format("http://%s:%s", - InetAddress.getLocalHost().getCanonicalHostName(), - Constants.DEFAULT_CRED_SERVICE_PORT)); - } catch (UnknownHostException e) { - LOG.error("Invalid CredService Url, configure it correctly."); + if (delegationToken == null || delegationToken.isEmpty()) { + LOG.error("Delegation Token not provided for initialization" + + " of RemoteSASKeyGenerator"); return false; } + this.delegationToken = delegationToken; + if (credServiceUrl == null || credServiceUrl.isEmpty()) { LOG.error("CredService Url not found in configuration to initialize" + " RemoteSASKeyGenerator"); @@ -142,17 +119,16 @@ public class RemoteSASKeyGeneratorImpl extends SASKeyGeneratorImpl { } remoteCallHelper = new WasbRemoteCallHelper(); - this.isSecurityEnabled = UserGroupInformation.isSecurityEnabled(); - this.isKerberosSupportEnabled = conf.getBoolean( - Constants.AZURE_KERBEROS_SUPPORT_PROPERTY_NAME, false); - LOG.debug("Initialization of RemoteSASKeyGenerator instance successful"); + LOG.debug("Initialization of RemoteSASKeyGenerator instance successfull"); return true; } @Override public URI getContainerSASUri(String storageAccount, String container) throws SASKeyGenerationException { + try { + LOG.debug("Generating Container SAS Key for Container {} " + "inside Storage Account {} ", container, storageAccount); URIBuilder uriBuilder = new URIBuilder(credServiceUrl); @@ -163,131 +139,84 @@ public class RemoteSASKeyGeneratorImpl extends SASKeyGeneratorImpl { container); uriBuilder.addParameter(SAS_EXPIRY_QUERY_PARAM_NAME, "" + getSasKeyExpiryPeriod()); - if (isSecurityEnabled && (delegationToken != null && !delegationToken - .isEmpty())) { - uriBuilder.addParameter(DELEGATION_TOKEN_QUERY_PARAM_NAME, - this.delegationToken); - } + uriBuilder.addParameter(DELEGATION_TOKEN_QUERY_PARAM_NAME, + this.delegationToken); - UserGroupInformation ugi = UserGroupInformation.getCurrentUser(); - UserGroupInformation connectUgi = ugi.getRealUser(); - if (connectUgi == null) { - connectUgi = ugi; + RemoteSASKeyGenerationResponse sasKeyResponse = + makeRemoteRequest(uriBuilder.build()); + + if (sasKeyResponse == null) { + throw new SASKeyGenerationException("RemoteSASKeyGenerationResponse" + + " object null from remote call"); + } else if (sasKeyResponse.getResponseCode() + == REMOTE_CALL_SUCCESS_CODE) { + return new URI(sasKeyResponse.getSasKey()); } else { - uriBuilder.addParameter(Constants.DOAS_PARAM, ugi.getShortUserName()); + throw new SASKeyGenerationException("Remote Service encountered error" + + " in SAS Key generation : " + + sasKeyResponse.getResponseMessage()); } - - if(isSecurityEnabled && !connectUgi.hasKerberosCredentials()){ - connectUgi = UserGroupInformation.getLoginUser(); - } - return getSASKey(uriBuilder.build(), connectUgi); } catch (URISyntaxException uriSyntaxEx) { throw new SASKeyGenerationException("Encountered URISyntaxException " + "while building the HttpGetRequest to remote cred service", uriSyntaxEx); - } catch (IOException e) { - throw new SASKeyGenerationException("Encountered IOException" - + " while building the HttpGetRequest to remote service", e); } } @Override public URI getRelativeBlobSASUri(String storageAccount, String container, String relativePath) throws SASKeyGenerationException { + try { + LOG.debug("Generating RelativePath SAS Key for relativePath {} inside" + " Container {} inside Storage Account {} ", relativePath, container, storageAccount); URIBuilder uriBuilder = new URIBuilder(credServiceUrl); uriBuilder.setPath("/" + BLOB_SAS_OP); - uriBuilder.addParameter(STORAGE_ACCOUNT_QUERY_PARAM_NAME, storageAccount); - uriBuilder.addParameter(CONTAINER_QUERY_PARAM_NAME, container); + uriBuilder.addParameter(STORAGE_ACCOUNT_QUERY_PARAM_NAME, + storageAccount); + uriBuilder.addParameter(CONTAINER_QUERY_PARAM_NAME, + container); uriBuilder.addParameter(RELATIVE_PATH_QUERY_PARAM_NAME, relativePath); uriBuilder.addParameter(SAS_EXPIRY_QUERY_PARAM_NAME, "" + getSasKeyExpiryPeriod()); + uriBuilder.addParameter(DELEGATION_TOKEN_QUERY_PARAM_NAME, + this.delegationToken); - if (isSecurityEnabled && (delegationToken != null && !delegationToken - .isEmpty())) { - uriBuilder.addParameter(DELEGATION_TOKEN_QUERY_PARAM_NAME, - this.delegationToken); + RemoteSASKeyGenerationResponse sasKeyResponse = + makeRemoteRequest(uriBuilder.build()); + + if (sasKeyResponse == null) { + throw new SASKeyGenerationException("RemoteSASKeyGenerationResponse" + + " object null from remote call"); + } else if (sasKeyResponse.getResponseCode() + == REMOTE_CALL_SUCCESS_CODE) { + return new URI(sasKeyResponse.getSasKey()); + } else { + throw new SASKeyGenerationException("Remote Service encountered error" + + " in SAS Key generation : " + + sasKeyResponse.getResponseMessage()); } - - UserGroupInformation ugi = UserGroupInformation.getCurrentUser(); - UserGroupInformation connectUgi = ugi.getRealUser(); - if (connectUgi == null) { - connectUgi = ugi; - } else{ - uriBuilder.addParameter(Constants.DOAS_PARAM, ugi.getShortUserName()); - } - - if(isSecurityEnabled && !connectUgi.hasKerberosCredentials()){ - connectUgi = UserGroupInformation.getLoginUser(); - } - return getSASKey(uriBuilder.build(), connectUgi); } catch (URISyntaxException uriSyntaxEx) { throw new SASKeyGenerationException("Encountered URISyntaxException" + " while building the HttpGetRequest to " + " remote service", uriSyntaxEx); - } catch (IOException e) { - throw new SASKeyGenerationException("Encountered IOException" - + " while building the HttpGetRequest to remote service", e); - } - } - - private URI getSASKey(final URI uri, UserGroupInformation connectUgi) - throws URISyntaxException, SASKeyGenerationException { - RemoteSASKeyGenerationResponse sasKeyResponse = null; - try { - connectUgi.checkTGTAndReloginFromKeytab(); - sasKeyResponse = connectUgi.doAs(new PrivilegedExceptionAction() { - @Override - public RemoteSASKeyGenerationResponse run() throws Exception { - AuthenticatedURL.Token token = null; - if (isKerberosSupportEnabled && UserGroupInformation.isSecurityEnabled() && ( - delegationToken == null || delegationToken.isEmpty())) { - token = new AuthenticatedURL.Token(); - final Authenticator kerberosAuthenticator = new KerberosDelegationTokenAuthenticator(); - kerberosAuthenticator.authenticate(uri.toURL(), token); - Validate.isTrue(token.isSet(), - "Authenticated Token is NOT present. The request cannot proceed."); - } - return makeRemoteRequest(uri, (token != null ? token.toString() : null)); - } - }); - } catch (InterruptedException e) { - LOG.error("Error fetching the SAS Key from Remote Service", e); - } catch (IOException e) { - LOG.error("Error fetching the SAS Key from Remote Service", e); - } - - if (sasKeyResponse == null) { - throw new SASKeyGenerationException( - "RemoteSASKeyGenerationResponse" + " object null from remote call"); - } else if (sasKeyResponse.getResponseCode() == REMOTE_CALL_SUCCESS_CODE) { - return new URI(sasKeyResponse.getSasKey()); - } else { - throw new SASKeyGenerationException("Remote Service encountered error" - + " in SAS Key generation : " + sasKeyResponse.getResponseMessage()); } } /** * Helper method to make a remote request. * @param uri - Uri to use for the remote request - * @param token - hadoop.auth token for the remote request * @return RemoteSASKeyGenerationResponse */ - private RemoteSASKeyGenerationResponse makeRemoteRequest(URI uri, String token) + private RemoteSASKeyGenerationResponse makeRemoteRequest(URI uri) throws SASKeyGenerationException { try { - HttpGet httpGet = new HttpGet(uri); - if(token != null){ - httpGet.setHeader("Cookie", AuthenticatedURL.AUTH_COOKIE + "=" + token); - } String responseBody = - remoteCallHelper.makeRemoteGetRequest(httpGet); + remoteCallHelper.makeRemoteGetRequest(new HttpGet(uri)); ObjectMapper objectMapper = new ObjectMapper(); return objectMapper.readValue(responseBody, diff --git a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/RemoteWasbAuthorizerImpl.java b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/RemoteWasbAuthorizerImpl.java deleted file mode 100644 index bb1093d3ff2..00000000000 --- a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/RemoteWasbAuthorizerImpl.java +++ /dev/null @@ -1,247 +0,0 @@ -/** - * 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.fs.azure; - -import java.io.IOException; -import java.net.InetAddress; -import java.net.URISyntaxException; -import java.security.PrivilegedExceptionAction; -import java.util.Iterator; - -import org.apache.commons.lang.Validate; -import org.apache.hadoop.conf.Configuration; - -import org.apache.hadoop.fs.azure.security.Constants; -import org.apache.hadoop.fs.azure.security.WasbDelegationTokenIdentifier; -import org.apache.hadoop.security.UserGroupInformation; -import org.apache.hadoop.security.authentication.client.AuthenticatedURL; -import org.apache.hadoop.security.authentication.client.Authenticator; -import org.apache.hadoop.security.token.Token; -import org.apache.hadoop.security.token.TokenIdentifier; -import org.apache.hadoop.security.token.delegation.web.KerberosDelegationTokenAuthenticator; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.utils.URIBuilder; - -import com.fasterxml.jackson.core.JsonParseException; -import org.codehaus.jackson.map.JsonMappingException; -import org.codehaus.jackson.map.ObjectMapper; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import static org.apache.hadoop.fs.azure.WasbRemoteCallHelper.REMOTE_CALL_SUCCESS_CODE; - -/** - * Class implementing WasbAuthorizerInterface using a remote - * service that implements the authorization operation. This - * class expects the url of the remote service to be passed - * via config. - */ -public class RemoteWasbAuthorizerImpl implements WasbAuthorizerInterface { - - public static final Logger LOG = - LoggerFactory.getLogger(RemoteWasbAuthorizerImpl.class); - private String remoteAuthorizerServiceUrl = ""; - - /** - * Configuration parameter name expected in the Configuration object to - * provide the url of the remote service. {@value} - */ - public static final String KEY_REMOTE_AUTH_SERVICE_URL = - "fs.azure.authorization.remote.service.url"; - - /** - * Authorization operation OP name in the remote service {@value} - */ - private static final String CHECK_AUTHORIZATION_OP = - "CHECK_AUTHORIZATION"; - - /** - * Query parameter specifying the access operation type. {@value} - */ - private static final String ACCESS_OPERATION_QUERY_PARAM_NAME = - "operation_type"; - - /** - * Query parameter specifying the wasb absolute path. {@value} - */ - private static final String WASB_ABSOLUTE_PATH_QUERY_PARAM_NAME = - "wasb_absolute_path"; - - /** - * Query parameter name for user info {@value} - */ - private static final String DELEGATION_TOKEN_QUERY_PARAM_NAME = - "delegation"; - - private WasbRemoteCallHelper remoteCallHelper = null; - private String delegationToken; - private boolean isSecurityEnabled; - private boolean isKerberosSupportEnabled; - - @Override - public void init(Configuration conf) - throws WasbAuthorizationException, IOException { - LOG.debug("Initializing RemoteWasbAuthorizerImpl instance"); - delegationToken = UserGroupInformation.getCurrentUser().getCredentials().getToken(WasbDelegationTokenIdentifier.TOKEN_KIND).encodeToUrlString(); - - remoteAuthorizerServiceUrl = conf.get(KEY_REMOTE_AUTH_SERVICE_URL, String - .format("http://%s:%s", - InetAddress.getLocalHost().getCanonicalHostName(), - Constants.DEFAULT_CRED_SERVICE_PORT)); - - if (remoteAuthorizerServiceUrl == null - || remoteAuthorizerServiceUrl.isEmpty()) { - throw new WasbAuthorizationException( - "fs.azure.authorization.remote.service.url config not set" - + " in configuration."); - } - - this.remoteCallHelper = new WasbRemoteCallHelper(); - this.isSecurityEnabled = UserGroupInformation.isSecurityEnabled(); - this.isKerberosSupportEnabled = conf.getBoolean( - Constants.AZURE_KERBEROS_SUPPORT_PROPERTY_NAME, false); - } - - @Override - public boolean authorize(String wasbAbsolutePath, String accessType) - throws WasbAuthorizationException, IOException { - try { - final URIBuilder uriBuilder = new URIBuilder(remoteAuthorizerServiceUrl); - uriBuilder.setPath("/" + CHECK_AUTHORIZATION_OP); - uriBuilder.addParameter(WASB_ABSOLUTE_PATH_QUERY_PARAM_NAME, - wasbAbsolutePath); - uriBuilder.addParameter(ACCESS_OPERATION_QUERY_PARAM_NAME, - accessType); - if (isSecurityEnabled && (delegationToken != null && !delegationToken - .isEmpty())) { - uriBuilder - .addParameter(DELEGATION_TOKEN_QUERY_PARAM_NAME, delegationToken); - } - String responseBody = null; - UserGroupInformation ugi = UserGroupInformation.getCurrentUser(); - UserGroupInformation connectUgi = ugi.getRealUser(); - if (connectUgi == null) { - connectUgi = ugi; - } else{ - uriBuilder.addParameter(Constants.DOAS_PARAM, ugi.getShortUserName()); - } - if(isSecurityEnabled && !connectUgi.hasKerberosCredentials()){ - connectUgi = UserGroupInformation.getLoginUser(); - } - connectUgi.checkTGTAndReloginFromKeytab(); - try { - responseBody = connectUgi.doAs(new PrivilegedExceptionAction(){ - @Override - public String run() throws Exception { - AuthenticatedURL.Token token = null; - HttpGet httpGet = new HttpGet(uriBuilder.build()); - if (isKerberosSupportEnabled && UserGroupInformation.isSecurityEnabled() && ( - delegationToken == null || delegationToken.isEmpty())) { - token = new AuthenticatedURL.Token(); - final Authenticator kerberosAuthenticator = new KerberosDelegationTokenAuthenticator(); - kerberosAuthenticator - .authenticate(uriBuilder.build().toURL(), token); - Validate.isTrue(token.isSet(), - "Authenticated Token is NOT present. The request cannot proceed."); - if(token != null){ - httpGet.setHeader("Cookie", AuthenticatedURL.AUTH_COOKIE + "=" + token); - } - } - return remoteCallHelper.makeRemoteGetRequest(httpGet); - }}); - } catch (InterruptedException e) { - LOG.error("Error in check authorization", e); - } - - ObjectMapper objectMapper = new ObjectMapper(); - RemoteAuthorizerResponse authorizerResponse = - objectMapper.readValue(responseBody, RemoteAuthorizerResponse.class); - - if (authorizerResponse == null) { - throw new WasbAuthorizationException( - "RemoteAuthorizerResponse object null from remote call"); - } else if (authorizerResponse.getResponseCode() - == REMOTE_CALL_SUCCESS_CODE) { - return authorizerResponse.getAuthorizationResult(); - } else { - throw new WasbAuthorizationException("Remote authorization" - + " serivce encountered an error " - + authorizerResponse.getResponseMessage()); - } - } catch (URISyntaxException | WasbRemoteCallException - | JsonParseException | JsonMappingException ex) { - throw new WasbAuthorizationException(ex); - } - } -} - -/** - * POJO representing the response expected from a remote - * authorization service. - * The remote service is expected to return the authorization - * response in the following JSON format - * { - * "responseCode" : 0 or non-zero , - * "responseMessage" : relavant message of failure - * "authorizationResult" : authorization result - * true - if auhorization allowed - * false - otherwise. - * - * } - */ -class RemoteAuthorizerResponse { - - private int responseCode; - private boolean authorizationResult; - private String responseMessage; - - public RemoteAuthorizerResponse(){ - } - - public RemoteAuthorizerResponse(int responseCode, - boolean authorizationResult, String message) { - this.responseCode = responseCode; - this.authorizationResult = authorizationResult; - this.responseMessage = message; - } - - public int getResponseCode() { - return responseCode; - } - - public void setResponseCode(int responseCode) { - this.responseCode = responseCode; - } - - public boolean getAuthorizationResult() { - return authorizationResult; - } - - public void setAuthorizationResult(boolean authorizationResult) { - this.authorizationResult = authorizationResult; - } - - public String getResponseMessage() { - return responseMessage; - } - - public void setResponseMessage(String message) { - this.responseMessage = message; - } -} \ No newline at end of file diff --git a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/SecureStorageInterfaceImpl.java b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/SecureStorageInterfaceImpl.java index 5ec9136a9a5..6749a76f7f4 100644 --- a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/SecureStorageInterfaceImpl.java +++ b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/SecureStorageInterfaceImpl.java @@ -69,17 +69,19 @@ public class SecureStorageInterfaceImpl extends StorageInterface { public static final String SAS_ERROR_CODE = "SAS Error"; private SASKeyGeneratorInterface sasKeyGenerator; private String storageAccount; + private String delegationToken; public SecureStorageInterfaceImpl(boolean useLocalSASKeyMode, - Configuration conf) + Configuration conf, String delegationToken) throws SecureModeException { + this.delegationToken = delegationToken; if (useLocalSASKeyMode) { this.sasKeyGenerator = new LocalSASKeyGeneratorImpl(conf); } else { RemoteSASKeyGeneratorImpl remoteSasKeyGenerator = new RemoteSASKeyGeneratorImpl(conf); - if (!remoteSasKeyGenerator.initialize(conf)) { + if (!remoteSasKeyGenerator.initialize(conf, this.delegationToken)) { throw new SecureModeException("Remote SAS Key mode could" + " not be initialized"); } diff --git a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/WasbAuthorizationException.java b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/WasbAuthorizationException.java deleted file mode 100644 index eff9248dffe..00000000000 --- a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/WasbAuthorizationException.java +++ /dev/null @@ -1,40 +0,0 @@ -/** - * 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.fs.azure; - -/** - * Exception that gets thrown during the authorization failures - * in WASB. - */ -public class WasbAuthorizationException extends AzureException { - - private static final long serialVersionUID = 1L; - - public WasbAuthorizationException(String message) { - super(message); - } - - public WasbAuthorizationException(String message, Throwable cause) { - super(message, cause); - } - - public WasbAuthorizationException(Throwable t) { - super(t); - } -} \ No newline at end of file diff --git a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/WasbAuthorizationOperations.java b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/WasbAuthorizationOperations.java deleted file mode 100644 index bd768372215..00000000000 --- a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/WasbAuthorizationOperations.java +++ /dev/null @@ -1,44 +0,0 @@ -/** - * 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.fs.azure; - -/** - * Different authorization operations supported - * in WASB. - */ - -public enum WasbAuthorizationOperations { - - READ, WRITE, EXECUTE; - - @Override - public String toString() { - switch(this) { - case READ: - return "read"; - case WRITE: - return "write"; - case EXECUTE: - return "execute"; - default: - throw new IllegalArgumentException( - "Invalid Auhtorization Operation"); - } - } -} \ No newline at end of file diff --git a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/WasbAuthorizerInterface.java b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/WasbAuthorizerInterface.java deleted file mode 100644 index 0c61997a61e..00000000000 --- a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/WasbAuthorizerInterface.java +++ /dev/null @@ -1,47 +0,0 @@ -/** - * 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.fs.azure; - -import java.io.IOException; - -import org.apache.hadoop.conf.Configuration; - -/** - * Interface to implement authorization support in WASB. - * API's of this interface will be implemented in the - * StorageInterface Layer before making calls to Azure - * Storage. - */ -public interface WasbAuthorizerInterface { - /** - * Initializer method - * @param conf - Configuration object - */ - public void init(Configuration conf) - throws WasbAuthorizationException, IOException; - - /** - * Authorizer API to authorize access in WASB. - * @param wasbAbsolutePath : Absolute WASB Path used for access. - * @param accessType : Type of access - * @return : true - If access allowed false - If access is not allowed. - */ - public boolean authorize(String wasbAbsolutePath, String accessType) - throws WasbAuthorizationException, IOException; -} \ No newline at end of file diff --git a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/security/Constants.java b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/security/Constants.java deleted file mode 100644 index 105068710d5..00000000000 --- a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/security/Constants.java +++ /dev/null @@ -1,54 +0,0 @@ -/** - * 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.fs.azure.security; - -/** - * Constants for used with WASB security implementation. - */ -public final class Constants { - - private Constants() { - } - - /** - * Configuration parameter name expected in the Configuration - * object to provide the url of the remote service {@value} - */ - public static final String KEY_CRED_SERVICE_URL = "fs.azure.cred.service.url"; - /** - * Default port of the remote service used as delegation token manager and Azure storage SAS key generator. - */ - public static final int DEFAULT_CRED_SERVICE_PORT = 50911; - - /** - * Default remote delegation token manager endpoint. - */ - public static final String DEFAULT_DELEGATION_TOKEN_MANAGER_ENDPOINT = "/tokenmanager/v1"; - - /** - * The configuration property to enable Kerberos support. - */ - - public static final String AZURE_KERBEROS_SUPPORT_PROPERTY_NAME = "fs.azure.enable.kerberos.support"; - - /** - * Parameter to be used for impersonation. - */ - public static final String DOAS_PARAM="doas"; -} diff --git a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/security/WasbDelegationTokenIdentifier.java b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/security/WasbDelegationTokenIdentifier.java deleted file mode 100644 index 530e04572e2..00000000000 --- a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/security/WasbDelegationTokenIdentifier.java +++ /dev/null @@ -1,48 +0,0 @@ -/** - * 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.fs.azure.security; - -import org.apache.hadoop.io.Text; -import org.apache.hadoop.security.token.delegation.web.DelegationTokenIdentifier; - -/** - * Delegation token Identifier for WASB delegation tokens. - */ -public class WasbDelegationTokenIdentifier extends DelegationTokenIdentifier { - public static final Text TOKEN_KIND = new Text("WASB delegation"); - - public WasbDelegationTokenIdentifier(){ - super(TOKEN_KIND); - } - - public WasbDelegationTokenIdentifier(Text kind) { - super(kind); - } - - public WasbDelegationTokenIdentifier(Text kind, Text owner, Text renewer, - Text realUser) { - super(kind, owner, renewer, realUser); - } - - @Override - public Text getKind() { - return TOKEN_KIND; - } - -} diff --git a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/security/WasbTokenRenewer.java b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/security/WasbTokenRenewer.java deleted file mode 100644 index 09b7349e120..00000000000 --- a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/security/WasbTokenRenewer.java +++ /dev/null @@ -1,124 +0,0 @@ -/** - * 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.fs.azure.security; - -import org.apache.hadoop.conf.Configuration; -import org.apache.hadoop.io.Text; -import org.apache.hadoop.security.UserGroupInformation; -import org.apache.hadoop.security.token.Token; -import org.apache.hadoop.security.token.TokenRenewer; -import org.apache.hadoop.security.token.delegation.AbstractDelegationTokenIdentifier; -import org.apache.hadoop.security.token.delegation.web.DelegationTokenAuthenticatedURL; -import org.apache.hadoop.security.token.delegation.web.DelegationTokenAuthenticator; -import org.apache.hadoop.security.token.delegation.web.KerberosDelegationTokenAuthenticator; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.net.InetAddress; -import java.net.URL; -import java.security.PrivilegedExceptionAction; - -/** - * Token Renewer for renewing WASB delegation tokens with remote service. - */ -public class WasbTokenRenewer extends TokenRenewer { - public static final Logger LOG = LoggerFactory - .getLogger(WasbTokenRenewer.class); - - @Override - public boolean handleKind(Text kind) { - return WasbDelegationTokenIdentifier.TOKEN_KIND.equals(kind); - } - - @Override - public boolean isManaged(Token token) throws IOException { - return true; - } - - @Override - public long renew(final Token token, Configuration conf) - throws IOException, InterruptedException { - LOG.debug("Renewing the delegation token"); - final UserGroupInformation ugi = UserGroupInformation.getCurrentUser(); - UserGroupInformation connectUgi = ugi.getRealUser(); - final UserGroupInformation proxyUser = connectUgi; - if (connectUgi == null) { - connectUgi = ugi; - } - if(!connectUgi.hasKerberosCredentials()){ - connectUgi = UserGroupInformation.getLoginUser(); - } - connectUgi.checkTGTAndReloginFromKeytab(); - final DelegationTokenAuthenticatedURL.Token authToken = new DelegationTokenAuthenticatedURL.Token(); - authToken - .setDelegationToken((Token) token); - final String credServiceUrl = conf.get(Constants.KEY_CRED_SERVICE_URL, - String.format("http://%s:%s", - InetAddress.getLocalHost().getCanonicalHostName(), - Constants.DEFAULT_CRED_SERVICE_PORT)); - DelegationTokenAuthenticator authenticator = new KerberosDelegationTokenAuthenticator(); - final DelegationTokenAuthenticatedURL authURL = new DelegationTokenAuthenticatedURL( - authenticator); - - return connectUgi.doAs(new PrivilegedExceptionAction() { - @Override - public Long run() throws Exception { - return authURL.renewDelegationToken(new URL(credServiceUrl - + Constants.DEFAULT_DELEGATION_TOKEN_MANAGER_ENDPOINT), - authToken, (proxyUser != null) ? ugi.getShortUserName() : null); - } - }); - } - - @Override - public void cancel(final Token token, Configuration conf) - throws IOException, InterruptedException { - LOG.debug("Cancelling the delegation token"); - final UserGroupInformation ugi = UserGroupInformation.getCurrentUser(); - UserGroupInformation connectUgi = ugi.getRealUser(); - final UserGroupInformation proxyUser = connectUgi; - if (connectUgi == null) { - connectUgi = ugi; - } - if(!connectUgi.hasKerberosCredentials()){ - connectUgi = UserGroupInformation.getLoginUser(); - } - connectUgi.checkTGTAndReloginFromKeytab(); - final DelegationTokenAuthenticatedURL.Token authToken = new DelegationTokenAuthenticatedURL.Token(); - authToken - .setDelegationToken((Token) token); - final String credServiceUrl = conf.get(Constants.KEY_CRED_SERVICE_URL, - String.format("http://%s:%s", - InetAddress.getLocalHost().getCanonicalHostName(), - Constants.DEFAULT_CRED_SERVICE_PORT)); - DelegationTokenAuthenticator authenticator = new KerberosDelegationTokenAuthenticator(); - final DelegationTokenAuthenticatedURL authURL = new DelegationTokenAuthenticatedURL( - authenticator); - connectUgi.doAs(new PrivilegedExceptionAction() { - @Override - public Void run() throws Exception { - authURL.cancelDelegationToken(new URL(credServiceUrl - + Constants.DEFAULT_DELEGATION_TOKEN_MANAGER_ENDPOINT), - authToken, (proxyUser != null) ? ugi.getShortUserName() : null); - return null; - } - }); - } -} \ No newline at end of file diff --git a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/security/package.html b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/security/package.html deleted file mode 100644 index fe58c0a272a..00000000000 --- a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/security/package.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - -

- Infrastructure for WASB client Security to work with Kerberos and Delegation - tokens. -

- - - diff --git a/hadoop-tools/hadoop-azure/src/main/resources/META-INF/services/org.apache.hadoop.security.token.TokenIdentifier b/hadoop-tools/hadoop-azure/src/main/resources/META-INF/services/org.apache.hadoop.security.token.TokenIdentifier deleted file mode 100644 index 7ec8216deb0..00000000000 --- a/hadoop-tools/hadoop-azure/src/main/resources/META-INF/services/org.apache.hadoop.security.token.TokenIdentifier +++ /dev/null @@ -1,16 +0,0 @@ -# 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. - -org.apache.hadoop.fs.azure.security.WasbDelegationTokenIdentifier \ No newline at end of file diff --git a/hadoop-tools/hadoop-azure/src/main/resources/META-INF/services/org.apache.hadoop.security.token.TokenRenewer b/hadoop-tools/hadoop-azure/src/main/resources/META-INF/services/org.apache.hadoop.security.token.TokenRenewer deleted file mode 100644 index f9c590aad8d..00000000000 --- a/hadoop-tools/hadoop-azure/src/main/resources/META-INF/services/org.apache.hadoop.security.token.TokenRenewer +++ /dev/null @@ -1,16 +0,0 @@ -# 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. - -org.apache.hadoop.fs.azure.security.WasbTokenRenewer \ No newline at end of file diff --git a/hadoop-tools/hadoop-azure/src/site/markdown/index.md b/hadoop-tools/hadoop-azure/src/site/markdown/index.md index a06d297ea7c..2865223acdb 100644 --- a/hadoop-tools/hadoop-azure/src/site/markdown/index.md +++ b/hadoop-tools/hadoop-azure/src/site/markdown/index.md @@ -333,40 +333,6 @@ The service is expected to return a response in JSON format: "sasKey" : Requested SAS Key } ``` - -## Authorization Support in WASB. - -Authorization support can be enabled in WASB using the following configuration: - -``` - - fs.azure.authorization - true - -``` - The current implementation of authorization relies on the presence of an external service that can enforce - the authorization. The service is expected to be running on a URL provided by the following config. - -``` - - fs.azure.authorization.remote.service.url - {URL} - -``` - - The remote service is expected to provide support for the following REST call: ```{URL}/CHECK_AUTHORIZATION``` - An example request: - ```{URL}/CHECK_AUTHORIZATION?wasb_absolute_path=&operation_type=&delegation_token=``` - - The service is expected to return a response in JSON format: - ``` - { - "responseCode" : 0 or non-zero , - "responseMessage" : relavant message on failure , - "authorizationResult" : true/false - } - ``` - ## Testing the hadoop-azure Module The hadoop-azure module includes a full suite of unit tests. Most of the tests diff --git a/hadoop-tools/hadoop-azure/src/test/java/org/apache/hadoop/fs/azure/MockWasbAuthorizerImpl.java b/hadoop-tools/hadoop-azure/src/test/java/org/apache/hadoop/fs/azure/MockWasbAuthorizerImpl.java deleted file mode 100644 index af5a537ce4d..00000000000 --- a/hadoop-tools/hadoop-azure/src/test/java/org/apache/hadoop/fs/azure/MockWasbAuthorizerImpl.java +++ /dev/null @@ -1,102 +0,0 @@ -/** - * 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.fs.azure; - -import java.util.HashMap; -import java.util.Map; - -import org.apache.hadoop.conf.Configuration; - -/** - * A mock wasb authorizer implementation. - */ - -public class MockWasbAuthorizerImpl implements WasbAuthorizerInterface { - - private Map authRules; - - @Override - public void init(Configuration conf) { - authRules = new HashMap(); - } - - public void addAuthRule(String wasbAbsolutePath, - String accessType, boolean access) { - AuthorizationComponent component = - new AuthorizationComponent(wasbAbsolutePath, accessType); - this.authRules.put(component, access); - } - - @Override - public boolean authorize(String wasbAbsolutePath, String accessType) - throws WasbAuthorizationException { - - AuthorizationComponent component = - new AuthorizationComponent(wasbAbsolutePath, accessType); - - if (authRules.containsKey(component)) { - return authRules.get(component); - } else { - return false; - } - } -} - -class AuthorizationComponent { - - private String wasbAbsolutePath; - private String accessType; - - public AuthorizationComponent(String wasbAbsolutePath, - String accessType) { - this.wasbAbsolutePath = wasbAbsolutePath; - this.accessType = accessType; - } - - @Override - public int hashCode() { - return this.wasbAbsolutePath.hashCode() ^ this.accessType.hashCode(); - } - - @Override - public boolean equals(Object obj) { - - if (obj == this) { - return true; - } - - if (obj == null - || !(obj instanceof AuthorizationComponent)) { - return false; - } - - return ((AuthorizationComponent)obj). - getWasbAbsolutePath().equals(this.wasbAbsolutePath) - && ((AuthorizationComponent)obj). - getAccessType().equals(this.accessType); - } - - public String getWasbAbsolutePath() { - return this.wasbAbsolutePath; - } - - public String getAccessType() { - return accessType; - } -} \ No newline at end of file diff --git a/hadoop-tools/hadoop-azure/src/test/java/org/apache/hadoop/fs/azure/TestNativeAzureFileSystemAuthorization.java b/hadoop-tools/hadoop-azure/src/test/java/org/apache/hadoop/fs/azure/TestNativeAzureFileSystemAuthorization.java deleted file mode 100644 index e76533550b4..00000000000 --- a/hadoop-tools/hadoop-azure/src/test/java/org/apache/hadoop/fs/azure/TestNativeAzureFileSystemAuthorization.java +++ /dev/null @@ -1,277 +0,0 @@ -/** - * 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.fs.azure; - -import org.apache.hadoop.conf.Configuration; -import org.apache.hadoop.fs.FSDataInputStream; -import org.apache.hadoop.fs.Path; -import org.junit.Test; - -import com.sun.tools.javac.util.Assert; - -/** - * Test class to hold all WASB authorization tests. - */ -public class TestNativeAzureFileSystemAuthorization - extends AbstractWasbTestBase { - - @Override - protected AzureBlobStorageTestAccount createTestAccount() throws Exception { - Configuration conf = new Configuration(); - conf.set(NativeAzureFileSystem.KEY_AZURE_AUTHORIZATION, "true"); - conf.set(RemoteWasbAuthorizerImpl.KEY_REMOTE_AUTH_SERVICE_URL, "test_url"); - return AzureBlobStorageTestAccount.create(conf); - } - - /** - * Positive test to verify Create and delete access check - * @throws Throwable - */ - @Test - public void testCreateAccessCheckPositive() throws Throwable { - - AzureBlobStorageTestAccount testAccount = createTestAccount(); - NativeAzureFileSystem fs = testAccount.getFileSystem(); - - String testFile = "test.dat"; - Path testPath = new Path(fs.getWorkingDirectory(), testFile); - - MockWasbAuthorizerImpl authorizer = new MockWasbAuthorizerImpl(); - authorizer.init(null); - authorizer.addAuthRule(testPath.toString(), - WasbAuthorizationOperations.WRITE.toString(), true); - authorizer.addAuthRule(testPath.toString(), - WasbAuthorizationOperations.EXECUTE.toString(), true); - fs.updateWasbAuthorizer(authorizer); - authorizer.addAuthRule(fs.getWorkingDirectory().toString(), - WasbAuthorizationOperations.EXECUTE.toString(), true); - - fs.create(testPath); - Assert.check(fs.exists(testPath)); - fs.delete(testPath, false); - } - - /** - * Negative test to verify Create access check - * @throws Throwable - */ - - @Test(expected=WasbAuthorizationException.class) - public void testCreateAccessCheckNegative() throws Throwable { - - AzureBlobStorageTestAccount testAccount = createTestAccount(); - NativeAzureFileSystem fs = testAccount.getFileSystem(); - - String testFile = "test.dat"; - Path testPath = new Path(fs.getWorkingDirectory(), testFile); - - MockWasbAuthorizerImpl authorizer = new MockWasbAuthorizerImpl(); - authorizer.init(null); - authorizer.addAuthRule(testPath.toString(), - WasbAuthorizationOperations.WRITE.toString(), false); - fs.updateWasbAuthorizer(authorizer); - - fs.create(new Path(testFile)); - } - - /** - * Positive test to verify Create and delete access check - * @throws Throwable - */ - @Test - public void testListAccessCheckPositive() throws Throwable { - - AzureBlobStorageTestAccount testAccount = createTestAccount(); - NativeAzureFileSystem fs = testAccount.getFileSystem(); - - String testFolder = "\\"; - Path testPath = new Path(fs.getWorkingDirectory(), testFolder); - - MockWasbAuthorizerImpl authorizer = new MockWasbAuthorizerImpl(); - authorizer.init(null); - authorizer.addAuthRule(testPath.toString(), - WasbAuthorizationOperations.EXECUTE.toString(), true); - fs.updateWasbAuthorizer(authorizer); - - fs.listStatus(testPath); - } - - /** - * Negative test to verify Create access check - * @throws Throwable - */ - - @Test(expected=WasbAuthorizationException.class) - public void testListAccessCheckNegative() throws Throwable { - - AzureBlobStorageTestAccount testAccount = createTestAccount(); - NativeAzureFileSystem fs = testAccount.getFileSystem(); - - String testFolder = "\\"; - Path testPath = new Path(fs.getWorkingDirectory(), testFolder); - - MockWasbAuthorizerImpl authorizer = new MockWasbAuthorizerImpl(); - authorizer.init(null); - authorizer.addAuthRule(testPath.toString(), - WasbAuthorizationOperations.EXECUTE.toString(), false); - fs.updateWasbAuthorizer(authorizer); - - fs.listStatus(testPath); - } - - /** - * Positive test to verify rename access check. - * @throws Throwable - */ - @Test - public void testRenameAccessCheckPositive() throws Throwable { - - AzureBlobStorageTestAccount testAccount = createTestAccount(); - NativeAzureFileSystem fs = testAccount.getFileSystem(); - - String testFile = "test.dat"; - Path testPath = new Path(fs.getWorkingDirectory(), testFile); - String renameFile = "test2.dat"; - Path renamePath = new Path(fs.getWorkingDirectory(), renameFile); - - MockWasbAuthorizerImpl authorizer = new MockWasbAuthorizerImpl(); - authorizer.init(null); - authorizer.addAuthRule(testPath.toString(), - WasbAuthorizationOperations.WRITE.toString(), true); - authorizer.addAuthRule(testPath.toString(), - WasbAuthorizationOperations.EXECUTE.toString(), true); - authorizer.addAuthRule(renamePath.toString(), - WasbAuthorizationOperations.EXECUTE.toString(), true); - authorizer.addAuthRule(fs.getWorkingDirectory().toString(), - WasbAuthorizationOperations.EXECUTE.toString(), true); - fs.updateWasbAuthorizer(authorizer); - fs.create(testPath); - - Assert.check(fs.exists(testPath)); - fs.rename(testPath, renamePath); - Assert.check(fs.exists(renamePath)); - fs.delete(renamePath, false); - } - - /** - * Negative test to verify rename access check. - * @throws Throwable - */ - @Test(expected=WasbAuthorizationException.class) - public void testRenameAccessCheckNegative() throws Throwable { - - AzureBlobStorageTestAccount testAccount = createTestAccount(); - NativeAzureFileSystem fs = testAccount.getFileSystem(); - String testFile = "test.dat"; - Path testPath = new Path(fs.getWorkingDirectory(), testFile); - Path renamePath = new Path("test2.dat"); - - MockWasbAuthorizerImpl authorizer = new MockWasbAuthorizerImpl(); - authorizer.init(null); - authorizer.addAuthRule(testPath.toString(), - WasbAuthorizationOperations.WRITE.toString(), true); - authorizer.addAuthRule(testPath.toString(), - WasbAuthorizationOperations.EXECUTE.toString(), false); - fs.updateWasbAuthorizer(authorizer); - - try { - fs.create(testPath); - - Assert.check(fs.exists(testPath)); - fs.rename(testPath, renamePath); - Assert.check(fs.exists(renamePath)); - fs.delete(renamePath, false); - } catch (WasbAuthorizationException ex) { - throw ex; - } finally { - authorizer = new MockWasbAuthorizerImpl(); - authorizer.init(null); - authorizer.addAuthRule(testPath.toString(), - WasbAuthorizationOperations.EXECUTE.toString(), false); - fs.updateWasbAuthorizer(authorizer); - Assert.check(fs.exists(testPath)); - fs.delete(testPath, false); - } - } - - /** - * Positive test for read access check. - * @throws Throwable - */ - @Test - public void testReadAccessCheckPositive() throws Throwable { - - AzureBlobStorageTestAccount testAccount = createTestAccount(); - NativeAzureFileSystem fs = testAccount.getFileSystem(); - String testFile = "test.dat"; - Path testPath = new Path(fs.getWorkingDirectory(), testFile); - MockWasbAuthorizerImpl authorizer = new MockWasbAuthorizerImpl(); - authorizer.init(null); - authorizer.addAuthRule(testPath.toString(), - WasbAuthorizationOperations.WRITE.toString(), true); - authorizer.addAuthRule(testPath.toString(), - WasbAuthorizationOperations.EXECUTE.toString(), true); - authorizer.addAuthRule(testPath.toString(), - WasbAuthorizationOperations.READ.toString(), true); - authorizer.addAuthRule(fs.getWorkingDirectory().toString(), - WasbAuthorizationOperations.EXECUTE.toString(), true); - fs.updateWasbAuthorizer(authorizer); - fs.create(testPath); - Assert.check(fs.exists(testPath)); - FSDataInputStream inputStream = fs.open(testPath); - inputStream.close(); - fs.delete(testPath, false); - } - - /** - * Negative test to verify read access check. - * @throws Throwable - */ - @Test(expected=WasbAuthorizationException.class) - public void testReadAccessCheckNegative() throws Throwable { - - AzureBlobStorageTestAccount testAccount = createTestAccount(); - NativeAzureFileSystem fs = testAccount.getFileSystem(); - String testFile = "test.dat"; - Path testPath = new Path(fs.getWorkingDirectory(), testFile); - MockWasbAuthorizerImpl authorizer = new MockWasbAuthorizerImpl(); - authorizer.init(null); - authorizer.addAuthRule(testPath.toString(), - WasbAuthorizationOperations.WRITE.toString(), true); - authorizer.addAuthRule(testPath.toString(), - WasbAuthorizationOperations.EXECUTE.toString(), true); - authorizer.addAuthRule(testPath.toString(), - WasbAuthorizationOperations.READ.toString(), false); - fs.updateWasbAuthorizer(authorizer); - - fs.create(new Path(testFile)); - Assert.check(fs.exists(testPath)); - FSDataInputStream inputStream = null; - try { - inputStream = fs.open(new Path(testFile)); - } catch (WasbAuthorizationException ex) { - throw ex; - } finally { - fs.delete(new Path(testFile), false); - if (inputStream != null) { - inputStream.close(); - } - } - } -} \ No newline at end of file From 686823529be09bea2a6cecb3503ef722017475bc Mon Sep 17 00:00:00 2001 From: Mingliang Liu Date: Mon, 6 Mar 2017 17:16:36 -0800 Subject: [PATCH 020/188] HADOOP-13930. Azure: Add Authorization support to WASB. Contributed by Sivaguru Sankaridurg and Dushyanth --- .../src/main/resources/core-default.xml | 10 + .../conf/TestCommonConfigurationFields.java | 1 + .../fs/azure/AzureNativeFileSystemStore.java | 5 +- .../fs/azure/NativeAzureFileSystem.java | 116 +++++- .../fs/azure/RemoteWasbAuthorizerImpl.java | 190 ++++++++++ .../fs/azure/WasbAuthorizationException.java | 40 ++ .../fs/azure/WasbAuthorizationOperations.java | 44 +++ .../fs/azure/WasbAuthorizerInterface.java | 53 +++ .../hadoop/fs/azure/WasbRemoteCallHelper.java | 71 +++- .../hadoop-azure/src/site/markdown/index.md | 34 ++ .../fs/azure/AzureBlobStorageTestAccount.java | 59 +-- .../fs/azure/MockWasbAuthorizerImpl.java | 102 ++++++ ...estNativeAzureFileSystemAuthorization.java | 344 ++++++++++++++++++ .../fs/azure/TestWasbRemoteCallHelper.java | 344 ++++++++++++++++++ .../src/test/resources/azure-test.xml | 28 +- 15 files changed, 1372 insertions(+), 69 deletions(-) create mode 100644 hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/RemoteWasbAuthorizerImpl.java create mode 100644 hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/WasbAuthorizationException.java create mode 100644 hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/WasbAuthorizationOperations.java create mode 100644 hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/WasbAuthorizerInterface.java create mode 100644 hadoop-tools/hadoop-azure/src/test/java/org/apache/hadoop/fs/azure/MockWasbAuthorizerImpl.java create mode 100644 hadoop-tools/hadoop-azure/src/test/java/org/apache/hadoop/fs/azure/TestNativeAzureFileSystemAuthorization.java create mode 100644 hadoop-tools/hadoop-azure/src/test/java/org/apache/hadoop/fs/azure/TestWasbRemoteCallHelper.java diff --git a/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml b/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml index 35be56bf81e..52b58eddb1e 100644 --- a/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml +++ b/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml @@ -1292,6 +1292,16 @@ to specify the time (such as 2s, 2m, 1h, etc.).
+ + fs.azure.authorization + false + + Config flag to enable authorization support in WASB. Setting it to "true" enables + authorization support to WASB. Currently WASB authorization requires a remote service + to provide authorization that needs to be specified via fs.azure.authorization.remote.service.url + configuration + + diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/conf/TestCommonConfigurationFields.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/conf/TestCommonConfigurationFields.java index 966a8ac0d08..cbfb6d1a7c2 100644 --- a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/conf/TestCommonConfigurationFields.java +++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/conf/TestCommonConfigurationFields.java @@ -114,6 +114,7 @@ public class TestCommonConfigurationFields extends TestConfigurationFieldsBase { xmlPropsToSkipCompare.add("fs.azure.sas.expiry.period"); xmlPropsToSkipCompare.add("fs.azure.local.sas.key.mode"); xmlPropsToSkipCompare.add("fs.azure.secure.mode"); + xmlPropsToSkipCompare.add("fs.azure.authorization"); // Deprecated properties. These should eventually be removed from the // class. diff --git a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/AzureNativeFileSystemStore.java b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/AzureNativeFileSystemStore.java index 07c389ce0c2..a8708ecafdf 100644 --- a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/AzureNativeFileSystemStore.java +++ b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/AzureNativeFileSystemStore.java @@ -249,7 +249,7 @@ public class AzureNativeFileSystemStore implements NativeFileSystemStore { * Default values to control SAS Key mode. * By default we set the values to false. */ - private static final boolean DEFAULT_USE_SECURE_MODE = false; + public static final boolean DEFAULT_USE_SECURE_MODE = false; private static final boolean DEFAULT_USE_LOCAL_SAS_KEY_MODE = false; /** @@ -849,6 +849,9 @@ public class AzureNativeFileSystemStore implements NativeFileSystemStore { rootDirectory = container.getDirectoryReference(""); canCreateOrModifyContainer = true; + + configureAzureStorageSession(); + tolerateOobAppends = false; } /** diff --git a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/NativeAzureFileSystem.java b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/NativeAzureFileSystem.java index 4184a539418..6de0a285a84 100644 --- a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/NativeAzureFileSystem.java +++ b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/NativeAzureFileSystem.java @@ -1106,7 +1106,31 @@ public class NativeAzureFileSystem extends FileSystem { // A counter to create unique (within-process) names for my metrics sources. private static AtomicInteger metricsSourceNameCounter = new AtomicInteger(); private boolean appendSupportEnabled = false; - + + /** + * Configuration key to enable authorization support in WASB. + */ + public static final String KEY_AZURE_AUTHORIZATION = + "fs.azure.authorization"; + + /** + * Default value for the authorization support in WASB. + */ + private static final boolean DEFAULT_AZURE_AUTHORIZATION = false; + + /** + * Flag controlling authorization support in WASB. + */ + private boolean azureAuthorization = false; + + /** + * Authorizer to use when authorization support is enabled in + * WASB. + */ + private WasbAuthorizerInterface authorizer = null; + + private String delegationToken = null; + public NativeAzureFileSystem() { // set store in initialize() } @@ -1146,11 +1170,11 @@ public class NativeAzureFileSystem extends FileSystem { return baseName + number; } } - + /** * Checks if the given URI scheme is a scheme that's affiliated with the Azure * File System. - * + * * @param scheme * The URI scheme. * @return true iff it's an Azure File System URI scheme. @@ -1167,7 +1191,7 @@ public class NativeAzureFileSystem extends FileSystem { /** * Puts in the authority of the default file system if it is a WASB file * system and the given URI's authority is null. - * + * * @return The URI with reconstructed authority if necessary and possible. */ private static URI reconstructAuthorityIfNeeded(URI uri, Configuration conf) { @@ -1237,6 +1261,24 @@ public class NativeAzureFileSystem extends FileSystem { // Initialize thread counts from user configuration deleteThreadCount = conf.getInt(AZURE_DELETE_THREADS, DEFAULT_AZURE_DELETE_THREADS); renameThreadCount = conf.getInt(AZURE_RENAME_THREADS, DEFAULT_AZURE_RENAME_THREADS); + + boolean useSecureMode = conf.getBoolean(AzureNativeFileSystemStore.KEY_USE_SECURE_MODE, + AzureNativeFileSystemStore.DEFAULT_USE_SECURE_MODE); + + this.azureAuthorization = useSecureMode && + conf.getBoolean(KEY_AZURE_AUTHORIZATION, DEFAULT_AZURE_AUTHORIZATION); + + if (this.azureAuthorization) { + + this.authorizer = + new RemoteWasbAuthorizerImpl(); + authorizer.init(conf); + } + } + + @VisibleForTesting + public void updateWasbAuthorizer(WasbAuthorizerInterface authorizer) { + this.authorizer = authorizer; } private NativeFileSystemStore createDefaultStore(Configuration conf) { @@ -1338,18 +1380,28 @@ public class NativeAzureFileSystem extends FileSystem { /** * For unit test purposes, retrieves the AzureNativeFileSystemStore store * backing this file system. - * + * * @return The store object. */ @VisibleForTesting public AzureNativeFileSystemStore getStore() { return actualStore; } - + NativeFileSystemStore getStoreInterface() { return store; } + private void performAuthCheck(String path, String accessType, + String operation) throws WasbAuthorizationException, IOException { + + if (azureAuthorization && this.authorizer != null && + !this.authorizer.authorize(path, accessType, delegationToken)) { + throw new WasbAuthorizationException(operation + + " operation for Path : " + path + " not allowed"); + } + } + /** * Gets the metrics source for this file system. * This is mainly here for unit testing purposes. @@ -1372,6 +1424,10 @@ public class NativeAzureFileSystem extends FileSystem { LOG.debug("Opening file: {} for append", f); Path absolutePath = makeAbsolute(f); + + performAuthCheck(absolutePath.toString(), + WasbAuthorizationOperations.WRITE.toString(), "append"); + String key = pathToKey(absolutePath); FileMetadata meta = null; try { @@ -1434,6 +1490,7 @@ public class NativeAzureFileSystem extends FileSystem { * Get a self-renewing lease on the specified file. * @param path path whose lease to be renewed. * @return Lease + * @throws AzureException when not being able to acquire a lease on the path */ public SelfRenewingLease acquireLease(Path path) throws AzureException { String fullKey = pathToKey(makeAbsolute(path)); @@ -1572,6 +1629,10 @@ public class NativeAzureFileSystem extends FileSystem { } Path absolutePath = makeAbsolute(f); + + performAuthCheck(absolutePath.toString(), + WasbAuthorizationOperations.WRITE.toString(), "create"); + String key = pathToKey(absolutePath); FileMetadata existingMetadata = store.retrieveMetadata(key); @@ -1652,10 +1713,10 @@ public class NativeAzureFileSystem extends FileSystem { // Construct the data output stream from the buffered output stream. FSDataOutputStream fsOut = new FSDataOutputStream(bufOutStream, statistics); - + // Increment the counter instrumentation.fileCreated(); - + // Return data output stream to caller. return fsOut; } @@ -1694,6 +1755,10 @@ public class NativeAzureFileSystem extends FileSystem { LOG.debug("Deleting file: {}", f.toString()); Path absolutePath = makeAbsolute(f); + + performAuthCheck(absolutePath.toString(), + WasbAuthorizationOperations.EXECUTE.toString(), "delete"); + String key = pathToKey(absolutePath); // Capture the metadata for the path. @@ -1964,6 +2029,10 @@ public class NativeAzureFileSystem extends FileSystem { // Capture the absolute path and the path to key. Path absolutePath = makeAbsolute(f); + + performAuthCheck(absolutePath.toString(), + WasbAuthorizationOperations.EXECUTE.toString(), "getFileStatus"); + String key = pathToKey(absolutePath); if (key.length() == 0) { // root always exists return newDirectory(null, absolutePath); @@ -2062,6 +2131,10 @@ public class NativeAzureFileSystem extends FileSystem { LOG.debug("Listing status for {}", f.toString()); Path absolutePath = makeAbsolute(f); + + performAuthCheck(absolutePath.toString(), + WasbAuthorizationOperations.EXECUTE.toString(), "list"); + String key = pathToKey(absolutePath); Set status = new TreeSet(); FileMetadata meta = null; @@ -2228,7 +2301,7 @@ public class NativeAzureFileSystem extends FileSystem { /** * Applies the applicable UMASK's on the given permission. - * + * * @param permission * The permission to mask. * @param applyMode @@ -2250,7 +2323,7 @@ public class NativeAzureFileSystem extends FileSystem { /** * Creates the PermissionStatus object to use for the given permission, based * on the current user in context. - * + * * @param permission * The permission for the file. * @return The permission status object to use. @@ -2284,6 +2357,10 @@ public class NativeAzureFileSystem extends FileSystem { } Path absolutePath = makeAbsolute(f); + + performAuthCheck(absolutePath.toString(), + WasbAuthorizationOperations.EXECUTE.toString(), "mkdirs"); + PermissionStatus permissionStatus = null; if(noUmask) { // ensure owner still has wx permissions at the minimum @@ -2337,6 +2414,10 @@ public class NativeAzureFileSystem extends FileSystem { LOG.debug("Opening file: {}", f.toString()); Path absolutePath = makeAbsolute(f); + + performAuthCheck(absolutePath.toString(), + WasbAuthorizationOperations.READ.toString(), "read"); + String key = pathToKey(absolutePath); FileMetadata meta = null; try { @@ -2393,7 +2474,12 @@ public class NativeAzureFileSystem extends FileSystem { + " through WASB that has colons in the name"); } - String srcKey = pathToKey(makeAbsolute(src)); + Path absolutePath = makeAbsolute(src); + + performAuthCheck(absolutePath.toString(), + WasbAuthorizationOperations.EXECUTE.toString(), "rename"); + + String srcKey = pathToKey(absolutePath); if (srcKey.length() == 0) { // Cannot rename root of file system @@ -2695,6 +2781,10 @@ public class NativeAzureFileSystem extends FileSystem { @Override public void setPermission(Path p, FsPermission permission) throws FileNotFoundException, IOException { Path absolutePath = makeAbsolute(p); + + performAuthCheck(absolutePath.toString(), + WasbAuthorizationOperations.EXECUTE.toString(), "setPermission"); + String key = pathToKey(absolutePath); FileMetadata metadata = null; try { @@ -2733,6 +2823,10 @@ public class NativeAzureFileSystem extends FileSystem { public void setOwner(Path p, String username, String groupname) throws IOException { Path absolutePath = makeAbsolute(p); + + performAuthCheck(absolutePath.toString(), + WasbAuthorizationOperations.EXECUTE.toString(), "setOwner"); + String key = pathToKey(absolutePath); FileMetadata metadata = null; diff --git a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/RemoteWasbAuthorizerImpl.java b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/RemoteWasbAuthorizerImpl.java new file mode 100644 index 00000000000..5f2265bc732 --- /dev/null +++ b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/RemoteWasbAuthorizerImpl.java @@ -0,0 +1,190 @@ +/** + * 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.fs.azure; + +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.annotations.VisibleForTesting; +import org.apache.hadoop.conf.Configuration; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.utils.URIBuilder; + +import java.io.IOException; +import java.net.URISyntaxException; + +import static org.apache.hadoop.fs.azure.WasbRemoteCallHelper.REMOTE_CALL_SUCCESS_CODE; + +/** + * Class implementing WasbAuthorizerInterface using a remote + * service that implements the authorization operation. This + * class expects the url of the remote service to be passed + * via config. + */ +public class RemoteWasbAuthorizerImpl implements WasbAuthorizerInterface { + + private String remoteAuthorizerServiceUrl = ""; + + /** + * Configuration parameter name expected in the Configuration object to + * provide the url of the remote service. {@value} + */ + public static final String KEY_REMOTE_AUTH_SERVICE_URL = + "fs.azure.authorization.remote.service.url"; + + /** + * Authorization operation OP name in the remote service {@value} + */ + private static final String CHECK_AUTHORIZATION_OP = + "CHECK_AUTHORIZATION"; + + /** + * Query parameter specifying the access operation type. {@value} + */ + private static final String ACCESS_OPERATION_QUERY_PARAM_NAME = + "operation_type"; + + /** + * Query parameter specifying the wasb absolute path. {@value} + */ + private static final String WASB_ABSOLUTE_PATH_QUERY_PARAM_NAME = + "wasb_absolute_path"; + + /** + * Query parameter name for user info {@value} + */ + private static final String DELEGATION_TOKEN_QUERY_PARAM_NAME = + "delegation_token"; + + private WasbRemoteCallHelper remoteCallHelper = null; + + @VisibleForTesting + public void updateWasbRemoteCallHelper(WasbRemoteCallHelper helper) { + this.remoteCallHelper = helper; + } + + @Override + public void init(Configuration conf) + throws WasbAuthorizationException, IOException { + + remoteAuthorizerServiceUrl = conf.get(KEY_REMOTE_AUTH_SERVICE_URL); + + if (remoteAuthorizerServiceUrl == null + || remoteAuthorizerServiceUrl.isEmpty()) { + throw new WasbAuthorizationException( + "fs.azure.authorization.remote.service.url config not set" + + " in configuration."); + } + + this.remoteCallHelper = new WasbRemoteCallHelper(); + } + + @Override + public boolean authorize(String wasbAbsolutePath, String accessType, + String delegationToken) throws WasbAuthorizationException, IOException { + + try { + URIBuilder uriBuilder = new URIBuilder(remoteAuthorizerServiceUrl); + uriBuilder.setPath("/" + CHECK_AUTHORIZATION_OP); + uriBuilder.addParameter(WASB_ABSOLUTE_PATH_QUERY_PARAM_NAME, + wasbAbsolutePath); + uriBuilder.addParameter(ACCESS_OPERATION_QUERY_PARAM_NAME, + accessType); + uriBuilder.addParameter(DELEGATION_TOKEN_QUERY_PARAM_NAME, + delegationToken); + + String responseBody = remoteCallHelper.makeRemoteGetRequest( + new HttpGet(uriBuilder.build())); + + ObjectMapper objectMapper = new ObjectMapper(); + RemoteAuthorizerResponse authorizerResponse = + objectMapper.readValue(responseBody, RemoteAuthorizerResponse.class); + + if (authorizerResponse == null) { + throw new WasbAuthorizationException( + "RemoteAuthorizerResponse object null from remote call"); + } else if (authorizerResponse.getResponseCode() + == REMOTE_CALL_SUCCESS_CODE) { + return authorizerResponse.getAuthorizationResult(); + } else { + throw new WasbAuthorizationException("Remote authorization" + + " service encountered an error " + + authorizerResponse.getResponseMessage()); + } + } catch (URISyntaxException | WasbRemoteCallException + | JsonParseException | JsonMappingException ex) { + throw new WasbAuthorizationException(ex); + } + } +} + +/** + * POJO representing the response expected from a remote + * authorization service. + * The remote service is expected to return the authorization + * response in the following JSON format + * { + * "responseCode" : 0 or non-zero , + * "responseMessage" : relavant message of failure + * "authorizationResult" : authorization result + * true - if auhorization allowed + * false - otherwise. + * + * } + */ +class RemoteAuthorizerResponse { + + private int responseCode; + private boolean authorizationResult; + private String responseMessage; + + public RemoteAuthorizerResponse(int responseCode, + boolean authorizationResult, String message) { + this.responseCode = responseCode; + this.authorizationResult = authorizationResult; + this.responseMessage = message; + } + + public RemoteAuthorizerResponse() { + } + + public int getResponseCode() { + return responseCode; + } + + public void setResponseCode(int responseCode) { + this.responseCode = responseCode; + } + + public boolean getAuthorizationResult() { + return authorizationResult; + } + + public void setAuthorizationResult(boolean authorizationResult) { + this.authorizationResult = authorizationResult; + } + + public String getResponseMessage() { + return responseMessage; + } + + public void setResponseMessage(String message) { + this.responseMessage = message; + } +} \ No newline at end of file diff --git a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/WasbAuthorizationException.java b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/WasbAuthorizationException.java new file mode 100644 index 00000000000..eff9248dffe --- /dev/null +++ b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/WasbAuthorizationException.java @@ -0,0 +1,40 @@ +/** + * 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.fs.azure; + +/** + * Exception that gets thrown during the authorization failures + * in WASB. + */ +public class WasbAuthorizationException extends AzureException { + + private static final long serialVersionUID = 1L; + + public WasbAuthorizationException(String message) { + super(message); + } + + public WasbAuthorizationException(String message, Throwable cause) { + super(message, cause); + } + + public WasbAuthorizationException(Throwable t) { + super(t); + } +} \ No newline at end of file diff --git a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/WasbAuthorizationOperations.java b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/WasbAuthorizationOperations.java new file mode 100644 index 00000000000..41ca2b32267 --- /dev/null +++ b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/WasbAuthorizationOperations.java @@ -0,0 +1,44 @@ +/** + * 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.fs.azure; + +/** + * Different authorization operations supported + * in WASB. + */ + +public enum WasbAuthorizationOperations { + + READ, WRITE, EXECUTE; + + @Override + public String toString() { + switch(this) { + case READ: + return "read"; + case WRITE: + return "write"; + case EXECUTE: + return "execute"; + default: + throw new IllegalArgumentException( + "Invalid Authorization Operation"); + } + } +} \ No newline at end of file diff --git a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/WasbAuthorizerInterface.java b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/WasbAuthorizerInterface.java new file mode 100644 index 00000000000..f391851095a --- /dev/null +++ b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/WasbAuthorizerInterface.java @@ -0,0 +1,53 @@ +/** + * 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.fs.azure; + +import java.io.IOException; + +import org.apache.hadoop.conf.Configuration; + +/** + * Interface to implement authorization support in WASB. + * API's of this interface will be implemented in the + * StorageInterface Layer before making calls to Azure + * Storage. + */ +public interface WasbAuthorizerInterface { + /** + * Initializer method + * @param conf - Configuration object + * @throws WasbAuthorizationException - On authorization exceptions + * @throws IOException - When not able to reach the authorizer + */ + public void init(Configuration conf) + throws WasbAuthorizationException, IOException; + + /** + * Authorizer API to authorize access in WASB. + + * @param wasbAbolutePath : Absolute WASB Path used for access. + * @param accessType : Type of access + * @param delegationToken : The user information. + * @return : true - If access allowed false - If access is not allowed. + * @throws WasbAuthorizationException - On authorization exceptions + * @throws IOException - When not able to reach the authorizer + */ + public boolean authorize(String wasbAbolutePath, String accessType, + String delegationToken) throws WasbAuthorizationException, IOException; +} \ No newline at end of file diff --git a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/WasbRemoteCallHelper.java b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/WasbRemoteCallHelper.java index 543c899f98e..09ea0847ee4 100644 --- a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/WasbRemoteCallHelper.java +++ b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/WasbRemoteCallHelper.java @@ -18,18 +18,21 @@ package org.apache.hadoop.fs.azure; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; - +import com.google.common.annotations.VisibleForTesting; +import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; +import org.apache.http.StatusLine; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.HttpClientBuilder; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; + /** * Helper class the has constants and helper methods * used in WASB when integrating with a remote http cred @@ -48,6 +51,11 @@ class WasbRemoteCallHelper { */ private HttpClient client = null; + @VisibleForTesting + public void updateHttpClient(HttpClient client) { + this.client = client; + } + public WasbRemoteCallHelper() { this.client = HttpClientBuilder.create().build(); } @@ -58,17 +66,54 @@ class WasbRemoteCallHelper { * @return Http Response body returned as a string. The caller * is expected to semantically understand the response. * @throws WasbRemoteCallException + * @throws IOException */ public String makeRemoteGetRequest(HttpGet getRequest) - throws WasbRemoteCallException { + throws WasbRemoteCallException, IOException { try { + final String APPLICATION_JSON = "application/json"; + final int MAX_CONTENT_LENGTH = 1024; + + getRequest.setHeader("Accept", APPLICATION_JSON); + HttpResponse response = client.execute(getRequest); - if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { - throw new WasbRemoteCallException( - response.getStatusLine().toString()); + StatusLine statusLine = response.getStatusLine(); + if (statusLine == null || statusLine.getStatusCode() != HttpStatus.SC_OK) { + throw new WasbRemoteCallException(getRequest.getURI().toString() + ":" + + ((statusLine!=null) ? statusLine.toString() : "NULL") + ); + } + + Header contentTypeHeader = response.getFirstHeader("Content-Type"); + if (contentTypeHeader == null || contentTypeHeader.getValue() != APPLICATION_JSON) { + throw new WasbRemoteCallException(getRequest.getURI().toString() + ":" + + "Content-Type mismatch: expected: " + APPLICATION_JSON + + ", got " + ((contentTypeHeader!=null) ? contentTypeHeader.getValue() : "NULL") + ); + } + + Header contentLengthHeader = response.getFirstHeader("Content-Length"); + if (contentLengthHeader == null) { + throw new WasbRemoteCallException(getRequest.getURI().toString() + ":" + + "Content-Length header missing" + ); + } + + try { + if (Integer.parseInt(contentLengthHeader.getValue()) > MAX_CONTENT_LENGTH) { + throw new WasbRemoteCallException(getRequest.getURI().toString() + ":" + + "Content-Length:" + contentLengthHeader.getValue() + + "exceeded max:" + MAX_CONTENT_LENGTH + ); + } + } + catch (NumberFormatException nfe) { + throw new WasbRemoteCallException(getRequest.getURI().toString() + ":" + + "Invalid Content-Length value :" + contentLengthHeader.getValue() + ); } BufferedReader rd = new BufferedReader( @@ -83,11 +128,11 @@ class WasbRemoteCallHelper { return responseBody.toString(); } catch (ClientProtocolException clientProtocolEx) { - throw new WasbRemoteCallException("Encountered ClientProtocolException" - + " while making remote call", clientProtocolEx); + throw new WasbRemoteCallException(getRequest.getURI().toString() + ":" + + "Encountered ClientProtocolException while making remote call", clientProtocolEx); } catch (IOException ioEx) { - throw new WasbRemoteCallException("Encountered IOException while making" - + " remote call", ioEx); + throw new WasbRemoteCallException(getRequest.getURI().toString() + ":" + + "Encountered IOException while making remote call", ioEx); } } } \ No newline at end of file diff --git a/hadoop-tools/hadoop-azure/src/site/markdown/index.md b/hadoop-tools/hadoop-azure/src/site/markdown/index.md index 2865223acdb..1d1274b930d 100644 --- a/hadoop-tools/hadoop-azure/src/site/markdown/index.md +++ b/hadoop-tools/hadoop-azure/src/site/markdown/index.md @@ -333,6 +333,40 @@ The service is expected to return a response in JSON format: "sasKey" : Requested SAS Key } ``` + +## Authorization Support in WASB. + +Authorization support can be enabled in WASB using the following configuration: + +``` + + fs.azure.authorization + true + +``` + The current implementation of authorization relies on the presence of an external service that can enforce + the authorization. The service is expected to be running on a URL provided by the following config. + +``` + + fs.azure.authorization.remote.service.url + {URL} + +``` + + The remote service is expected to provide support for the following REST call: ```{URL}/CHECK_AUTHORIZATION``` + An example request: + ```{URL}/CHECK_AUTHORIZATION?wasb_absolute_path=&operation_type=&delegation_token=``` + + The service is expected to return a response in JSON format: + ``` + { + "responseCode" : 0 or non-zero , + "responseMessage" : relevant message on failure , + "authorizationResult" : true/false + } + ``` + ## Testing the hadoop-azure Module The hadoop-azure module includes a full suite of unit tests. Most of the tests diff --git a/hadoop-tools/hadoop-azure/src/test/java/org/apache/hadoop/fs/azure/AzureBlobStorageTestAccount.java b/hadoop-tools/hadoop-azure/src/test/java/org/apache/hadoop/fs/azure/AzureBlobStorageTestAccount.java index 5353663bc89..5f66fd2f0ac 100644 --- a/hadoop-tools/hadoop-azure/src/test/java/org/apache/hadoop/fs/azure/AzureBlobStorageTestAccount.java +++ b/hadoop-tools/hadoop-azure/src/test/java/org/apache/hadoop/fs/azure/AzureBlobStorageTestAccount.java @@ -18,17 +18,9 @@ package org.apache.hadoop.fs.azure; -import static org.apache.hadoop.fs.azure.AzureNativeFileSystemStore.DEFAULT_STORAGE_EMULATOR_ACCOUNT_NAME; - -import java.net.URI; -import java.net.URISyntaxException; -import java.util.Calendar; -import java.util.Date; -import java.util.EnumSet; -import java.util.GregorianCalendar; -import java.util.TimeZone; -import java.util.concurrent.ConcurrentLinkedQueue; - +import com.microsoft.azure.storage.*; +import com.microsoft.azure.storage.blob.*; +import com.microsoft.azure.storage.core.Base64; import org.apache.commons.configuration2.SubsetConfiguration; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; @@ -39,22 +31,14 @@ import org.apache.hadoop.metrics2.MetricsRecord; import org.apache.hadoop.metrics2.MetricsSink; import org.apache.hadoop.metrics2.MetricsTag; import org.apache.hadoop.metrics2.impl.TestMetricsConfig; -import org.apache.hadoop.metrics2.lib.DefaultMetricsSystem; -import com.microsoft.azure.storage.AccessCondition; -import com.microsoft.azure.storage.CloudStorageAccount; -import com.microsoft.azure.storage.StorageCredentials; -import com.microsoft.azure.storage.StorageCredentialsAccountAndKey; -import com.microsoft.azure.storage.StorageCredentialsAnonymous; -import com.microsoft.azure.storage.blob.BlobContainerPermissions; -import com.microsoft.azure.storage.blob.BlobContainerPublicAccessType; -import com.microsoft.azure.storage.blob.BlobOutputStream; -import com.microsoft.azure.storage.blob.CloudBlobClient; -import com.microsoft.azure.storage.blob.CloudBlobContainer; -import com.microsoft.azure.storage.blob.CloudBlockBlob; -import com.microsoft.azure.storage.blob.SharedAccessBlobPermissions; -import com.microsoft.azure.storage.blob.SharedAccessBlobPolicy; -import com.microsoft.azure.storage.core.Base64; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.*; +import java.util.concurrent.ConcurrentLinkedQueue; + +import static org.apache.hadoop.fs.azure.AzureNativeFileSystemStore.DEFAULT_STORAGE_EMULATOR_ACCOUNT_NAME; +import static org.apache.hadoop.fs.azure.AzureNativeFileSystemStore.KEY_USE_LOCAL_SAS_KEY_MODE; /** * Helper class to create WASB file systems backed by either a mock in-memory @@ -92,7 +76,7 @@ public final class AzureBlobStorageTestAccount { private static final ConcurrentLinkedQueue allMetrics = new ConcurrentLinkedQueue(); private static boolean metricsConfigSaved = false; - + private AzureBlobStorageTestAccount(NativeAzureFileSystem fs, CloudStorageAccount account, CloudBlobContainer container) { @@ -272,6 +256,7 @@ public final class AzureBlobStorageTestAccount { store.setAzureStorageInteractionLayer(mockStorage); NativeAzureFileSystem fs = new NativeAzureFileSystem(store); setMockAccountKey(conf); + configureSecureModeTestSettings(conf); // register the fs provider. fs.initialize(new URI(MOCK_WASB_URI), conf); @@ -332,6 +317,8 @@ public final class AzureBlobStorageTestAccount { // Set account URI and initialize Azure file system. URI accountUri = createAccountUri(DEFAULT_STORAGE_EMULATOR_ACCOUNT_NAME, containerName); + configureSecureModeTestSettings(conf); + fs.initialize(accountUri, conf); // Create test account initializing the appropriate member variables. @@ -368,6 +355,7 @@ public final class AzureBlobStorageTestAccount { // out-of-band appends. conf.setBoolean(KEY_DISABLE_THROTTLING, true); conf.setBoolean(KEY_READ_TOLERATE_CONCURRENT_APPEND, true); + configureSecureModeTestSettings(conf); // Set account URI and initialize Azure file system. URI accountUri = createAccountUri(accountName, containerName); @@ -408,6 +396,17 @@ public final class AzureBlobStorageTestAccount { setMockAccountKey(conf, MOCK_ACCOUNT_NAME); } + /** + * Configure default values for Secure Mode testing. + * These values are relevant only when testing in Secure Mode. + * + * @param conf + * The configuration. + */ + public static void configureSecureModeTestSettings(Configuration conf) { + conf.set(KEY_USE_LOCAL_SAS_KEY_MODE, "true"); // always use local sas-key mode for testing + } + /** * Sets the mock account key in the given configuration. * @@ -556,6 +555,8 @@ public final class AzureBlobStorageTestAccount { conf.setBoolean(KEY_DISABLE_THROTTLING, true); } + configureSecureModeTestSettings(conf); + // Set account URI and initialize Azure file system. URI accountUri = createAccountUri(accountName, containerName); fs.initialize(accountUri, conf); @@ -693,6 +694,8 @@ public final class AzureBlobStorageTestAccount { // Capture the account URL and the account name. String accountName = conf.get(TEST_ACCOUNT_NAME_PROPERTY_NAME); + configureSecureModeTestSettings(conf); + // Generate a container name and create a shared access signature string for // it. // @@ -764,6 +767,8 @@ public final class AzureBlobStorageTestAccount { // Capture the account URL and the account name. String accountName = conf.get(TEST_ACCOUNT_NAME_PROPERTY_NAME); + configureSecureModeTestSettings(conf); + // Set up public container with the specified blob name. CloudBlockBlob blobRoot = primeRootContainer(blobClient, accountName, blobName, fileSize); diff --git a/hadoop-tools/hadoop-azure/src/test/java/org/apache/hadoop/fs/azure/MockWasbAuthorizerImpl.java b/hadoop-tools/hadoop-azure/src/test/java/org/apache/hadoop/fs/azure/MockWasbAuthorizerImpl.java new file mode 100644 index 00000000000..8f7cb2ae5ea --- /dev/null +++ b/hadoop-tools/hadoop-azure/src/test/java/org/apache/hadoop/fs/azure/MockWasbAuthorizerImpl.java @@ -0,0 +1,102 @@ +/** + * 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.fs.azure; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.hadoop.conf.Configuration; + +/** + * A mock wasb authorizer implementation. + */ + +public class MockWasbAuthorizerImpl implements WasbAuthorizerInterface { + + private Map authRules; + + @Override + public void init(Configuration conf) { + authRules = new HashMap(); + } + + public void addAuthRule(String wasbAbsolutePath, + String accessType, boolean access) { + AuthorizationComponent component = + new AuthorizationComponent(wasbAbsolutePath, accessType); + this.authRules.put(component, access); + } + + @Override + public boolean authorize(String wasbAbsolutePath, String accessType, + String delegationToken) throws WasbAuthorizationException { + + AuthorizationComponent component = + new AuthorizationComponent(wasbAbsolutePath, accessType); + + if (authRules.containsKey(component)) { + return authRules.get(component); + } else { + return false; + } + } +} + +class AuthorizationComponent { + + private String wasbAbsolutePath; + private String accessType; + + public AuthorizationComponent(String wasbAbsolutePath, + String accessType) { + this.wasbAbsolutePath = wasbAbsolutePath; + this.accessType = accessType; + } + + @Override + public int hashCode() { + return this.wasbAbsolutePath.hashCode() ^ this.accessType.hashCode(); + } + + @Override + public boolean equals(Object obj) { + + if (obj == this) { + return true; + } + + if (obj == null + || !(obj instanceof AuthorizationComponent)) { + return false; + } + + return ((AuthorizationComponent)obj). + getWasbAbsolutePath().equals(this.wasbAbsolutePath) + && ((AuthorizationComponent)obj). + getAccessType().equals(this.accessType); + } + + public String getWasbAbsolutePath() { + return this.wasbAbsolutePath; + } + + public String getAccessType() { + return accessType; + } +} \ No newline at end of file diff --git a/hadoop-tools/hadoop-azure/src/test/java/org/apache/hadoop/fs/azure/TestNativeAzureFileSystemAuthorization.java b/hadoop-tools/hadoop-azure/src/test/java/org/apache/hadoop/fs/azure/TestNativeAzureFileSystemAuthorization.java new file mode 100644 index 00000000000..a2bbeb1765d --- /dev/null +++ b/hadoop-tools/hadoop-azure/src/test/java/org/apache/hadoop/fs/azure/TestNativeAzureFileSystemAuthorization.java @@ -0,0 +1,344 @@ +/** + * 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.fs.azure; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.FSDataInputStream; +import org.apache.hadoop.fs.FSDataOutputStream; +import org.apache.hadoop.fs.Path; +import org.apache.hadoop.fs.contract.ContractTestUtils; +import org.junit.Assume; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import com.sun.tools.javac.util.Assert; +import org.junit.rules.ExpectedException; + +import java.io.Console; + +import static org.apache.hadoop.fs.azure.AzureNativeFileSystemStore.KEY_USE_SECURE_MODE; + +/** + * Test class to hold all WASB authorization tests. + */ +public class TestNativeAzureFileSystemAuthorization + extends AbstractWasbTestBase { + + @Override + protected AzureBlobStorageTestAccount createTestAccount() throws Exception { + Configuration conf = new Configuration(); + conf.set(NativeAzureFileSystem.KEY_AZURE_AUTHORIZATION, "true"); + conf.set(RemoteWasbAuthorizerImpl.KEY_REMOTE_AUTH_SERVICE_URL, "http://localhost/"); + return AzureBlobStorageTestAccount.create(conf); + } + + + @Before + public void beforeMethod() { + boolean useSecureMode = fs.getConf().getBoolean(KEY_USE_SECURE_MODE, false); + boolean useAuthorization = fs.getConf().getBoolean(NativeAzureFileSystem.KEY_AZURE_AUTHORIZATION, false); + Assume.assumeTrue("Test valid when both SecureMode and Authorization are enabled .. skipping", + useSecureMode && useAuthorization); + + Assume.assumeTrue( + useSecureMode && useAuthorization + ); + } + + + @Rule + public ExpectedException expectedEx = ExpectedException.none(); + + /** + * Positive test to verify Create and delete access check + * @throws Throwable + */ + @Test + public void testCreateAccessCheckPositive() throws Throwable { + + AzureBlobStorageTestAccount testAccount = createTestAccount(); + NativeAzureFileSystem fs = testAccount.getFileSystem(); + + Path parentDir = new Path("/testCreateAccessCheckPositive"); + Path testPath = new Path(parentDir, "test.dat"); + + MockWasbAuthorizerImpl authorizer = new MockWasbAuthorizerImpl(); + authorizer.init(null); + authorizer.addAuthRule(testPath.toString(), WasbAuthorizationOperations.WRITE.toString(), true); + authorizer.addAuthRule(testPath.toString(), WasbAuthorizationOperations.EXECUTE.toString(), true); + authorizer.addAuthRule(parentDir.toString(), WasbAuthorizationOperations.EXECUTE.toString(), true); + fs.updateWasbAuthorizer(authorizer); + + fs.create(testPath); + ContractTestUtils.assertPathExists(fs, "testPath was not created", testPath); + fs.delete(parentDir, true); + } + + /** + * Negative test to verify Create access check + * @throws Throwable + */ + + @Test // (expected=WasbAuthorizationException.class) + public void testCreateAccessCheckNegative() throws Throwable { + + expectedEx.expect(WasbAuthorizationException.class); + expectedEx.expectMessage("create operation for Path : /testCreateAccessCheckNegative/test.dat not allowed"); + + AzureBlobStorageTestAccount testAccount = createTestAccount(); + NativeAzureFileSystem fs = testAccount.getFileSystem(); + + Path parentDir = new Path("/testCreateAccessCheckNegative"); + Path testPath = new Path(parentDir, "test.dat"); + + MockWasbAuthorizerImpl authorizer = new MockWasbAuthorizerImpl(); + authorizer.init(null); + authorizer.addAuthRule(testPath.toString(),WasbAuthorizationOperations.WRITE.toString(), false); + authorizer.addAuthRule(parentDir.toString(),WasbAuthorizationOperations.EXECUTE.toString(), true); + fs.updateWasbAuthorizer(authorizer); + + try { + fs.create(testPath); + } + finally { + fs.delete(parentDir, true); + } + } + + /** + * Positive test to verify Create and delete access check + * @throws Throwable + */ + @Test + public void testListAccessCheckPositive() throws Throwable { + + AzureBlobStorageTestAccount testAccount = createTestAccount(); + NativeAzureFileSystem fs = testAccount.getFileSystem(); + + Path parentDir = new Path("/testListAccessCheckPositive"); + Path testPath = new Path(parentDir, "test.dat"); + + MockWasbAuthorizerImpl authorizer = new MockWasbAuthorizerImpl(); + authorizer.init(null); + authorizer.addAuthRule(testPath.toString(), WasbAuthorizationOperations.WRITE.toString(), true); + authorizer.addAuthRule(testPath.toString(), WasbAuthorizationOperations.EXECUTE.toString(), true); + authorizer.addAuthRule(parentDir.toString(), WasbAuthorizationOperations.EXECUTE.toString(), true); + fs.updateWasbAuthorizer(authorizer); + + fs.create(testPath); + ContractTestUtils.assertPathExists(fs, "testPath does not exist", testPath); + + try { + fs.listStatus(testPath); + } + finally { + fs.delete(parentDir, true); + } + } + + /** + * Negative test to verify Create access check + * @throws Throwable + */ + + @Test //(expected=WasbAuthorizationException.class) + public void testListAccessCheckNegative() throws Throwable { + + expectedEx.expect(WasbAuthorizationException.class); + expectedEx.expectMessage("getFileStatus operation for Path : /testListAccessCheckNegative/test.dat not allowed"); + + AzureBlobStorageTestAccount testAccount = createTestAccount(); + NativeAzureFileSystem fs = testAccount.getFileSystem(); + + Path parentDir = new Path("/testListAccessCheckNegative"); + Path testPath = new Path(parentDir, "test.dat"); + + MockWasbAuthorizerImpl authorizer = new MockWasbAuthorizerImpl(); + authorizer.init(null); + authorizer.addAuthRule(testPath.toString(), WasbAuthorizationOperations.WRITE.toString(), true); + authorizer.addAuthRule(testPath.toString(), WasbAuthorizationOperations.EXECUTE.toString(), false); + authorizer.addAuthRule(parentDir.toString(), WasbAuthorizationOperations.EXECUTE.toString(), true); + fs.updateWasbAuthorizer(authorizer); + + fs.create(testPath); + ContractTestUtils.assertPathExists(fs, "testPath does not exist", testPath); + + try { + fs.listStatus(testPath); + } + finally { + fs.delete(parentDir, true); + } + } + + /** + * Positive test to verify rename access check. + * @throws Throwable + */ + @Test + public void testRenameAccessCheckPositive() throws Throwable { + + AzureBlobStorageTestAccount testAccount = createTestAccount(); + NativeAzureFileSystem fs = testAccount.getFileSystem(); + + Path parentDir = new Path("/testRenameAccessCheckPositive"); + Path testPath = new Path(parentDir, "test.dat"); + Path renamePath = new Path(parentDir, "test2.dat"); + + MockWasbAuthorizerImpl authorizer = new MockWasbAuthorizerImpl(); + authorizer.init(null); + authorizer.addAuthRule(testPath.toString(), WasbAuthorizationOperations.WRITE.toString(), true); + authorizer.addAuthRule(testPath.toString(), WasbAuthorizationOperations.EXECUTE.toString(), true); + authorizer.addAuthRule(renamePath.toString(), WasbAuthorizationOperations.EXECUTE.toString(), true); + authorizer.addAuthRule(parentDir.toString(), WasbAuthorizationOperations.EXECUTE.toString(), true); + fs.updateWasbAuthorizer(authorizer); + + fs.create(testPath); + ContractTestUtils.assertPathExists(fs, "sourcePath does not exist", testPath); + + try { + fs.rename(testPath, renamePath); + ContractTestUtils.assertPathExists(fs, "destPath does not exist", renamePath); + } + finally { + fs.delete(parentDir, true); + } + } + + /** + * Negative test to verify rename access check. + * @throws Throwable + */ + @Test //(expected=WasbAuthorizationException.class) + public void testRenameAccessCheckNegative() throws Throwable { + + expectedEx.expect(WasbAuthorizationException.class); + expectedEx.expectMessage("rename operation for Path : /testRenameAccessCheckNegative/test.dat not allowed"); + + AzureBlobStorageTestAccount testAccount = createTestAccount(); + NativeAzureFileSystem fs = testAccount.getFileSystem(); + Path parentDir = new Path("/testRenameAccessCheckNegative"); + Path testPath = new Path(parentDir, "test.dat"); + Path renamePath = new Path(parentDir, "test2.dat"); + + MockWasbAuthorizerImpl authorizer = new MockWasbAuthorizerImpl(); + authorizer.init(null); + authorizer.addAuthRule(testPath.toString(), WasbAuthorizationOperations.WRITE.toString(), true); + // set EXECUTE to true for initial assert right after creation. + authorizer.addAuthRule(testPath.toString(), WasbAuthorizationOperations.EXECUTE.toString(), true); + authorizer.addAuthRule(parentDir.toString(), WasbAuthorizationOperations.EXECUTE.toString(), true); + fs.updateWasbAuthorizer(authorizer); + + fs.create(testPath); + ContractTestUtils.assertPathExists(fs, "sourcePath does not exist", testPath); + + // Set EXECUTE to false for actual rename-failure test + authorizer.addAuthRule(testPath.toString(), WasbAuthorizationOperations.EXECUTE.toString(), false); + fs.updateWasbAuthorizer(authorizer); + + try { + fs.rename(testPath, renamePath); + ContractTestUtils.assertPathExists(fs, "destPath does not exist", renamePath); + } finally { + fs.delete(parentDir, true); + } + } + + /** + * Positive test for read access check. + * @throws Throwable + */ + @Test + public void testReadAccessCheckPositive() throws Throwable { + + AzureBlobStorageTestAccount testAccount = createTestAccount(); + NativeAzureFileSystem fs = testAccount.getFileSystem(); + Path parentDir = new Path("/testReadAccessCheckPositive"); + Path testPath = new Path(parentDir, "test.dat"); + + MockWasbAuthorizerImpl authorizer = new MockWasbAuthorizerImpl(); + authorizer.init(null); + authorizer.addAuthRule(testPath.toString(), WasbAuthorizationOperations.WRITE.toString(), true); + authorizer.addAuthRule(testPath.toString(), WasbAuthorizationOperations.EXECUTE.toString(), true); + authorizer.addAuthRule(testPath.toString(), WasbAuthorizationOperations.READ.toString(), true); + authorizer.addAuthRule(parentDir.toString(), WasbAuthorizationOperations.EXECUTE.toString(), true); + fs.updateWasbAuthorizer(authorizer); + + FSDataOutputStream fso = fs.create(testPath); + String data = "Hello World"; + fso.writeBytes(data); + fso.close(); + ContractTestUtils.assertPathExists(fs, "testPath does not exist", testPath); + + FSDataInputStream inputStream = null; + try { + inputStream = fs.open(testPath); + ContractTestUtils.verifyRead(inputStream, data.getBytes(), 0, data.length()); + } + finally { + if(inputStream != null) { + inputStream.close(); + } + fs.delete(parentDir, true); + } + } + + /** + * Negative test to verify read access check. + * @throws Throwable + */ + + @Test //(expected=WasbAuthorizationException.class) + public void testReadAccessCheckNegative() throws Throwable { + + expectedEx.expect(WasbAuthorizationException.class); + expectedEx.expectMessage("read operation for Path : /testReadAccessCheckNegative/test.dat not allowed"); + + AzureBlobStorageTestAccount testAccount = createTestAccount(); + NativeAzureFileSystem fs = testAccount.getFileSystem(); + Path parentDir = new Path("/testReadAccessCheckNegative"); + Path testPath = new Path(parentDir, "test.dat"); + + MockWasbAuthorizerImpl authorizer = new MockWasbAuthorizerImpl(); + authorizer.init(null); + authorizer.addAuthRule(testPath.toString(), WasbAuthorizationOperations.WRITE.toString(), true); + authorizer.addAuthRule(testPath.toString(), WasbAuthorizationOperations.EXECUTE.toString(), true); + authorizer.addAuthRule(testPath.toString(), WasbAuthorizationOperations.READ.toString(), false); + authorizer.addAuthRule(parentDir.toString(), WasbAuthorizationOperations.EXECUTE.toString(), true); + fs.updateWasbAuthorizer(authorizer); + + FSDataOutputStream fso = fs.create(testPath); + String data = "Hello World"; + fso.writeBytes(data); + fso.close(); + ContractTestUtils.assertPathExists(fs, "testPath does not exist", testPath); + + FSDataInputStream inputStream = null; + try { + inputStream = fs.open(testPath); + ContractTestUtils.verifyRead(inputStream, data.getBytes(), 0, data.length()); + } finally { + if (inputStream != null) { + inputStream.close(); + } + fs.delete(parentDir, true); + } + } +} \ No newline at end of file diff --git a/hadoop-tools/hadoop-azure/src/test/java/org/apache/hadoop/fs/azure/TestWasbRemoteCallHelper.java b/hadoop-tools/hadoop-azure/src/test/java/org/apache/hadoop/fs/azure/TestWasbRemoteCallHelper.java new file mode 100644 index 00000000000..b13e5e90574 --- /dev/null +++ b/hadoop-tools/hadoop-azure/src/test/java/org/apache/hadoop/fs/azure/TestWasbRemoteCallHelper.java @@ -0,0 +1,344 @@ +/** + * 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.fs.azure; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.Path; +import org.apache.hadoop.fs.contract.ContractTestUtils; +import org.apache.http.*; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.junit.Assume; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.Mockito; + +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; + +import static org.apache.hadoop.fs.azure.AzureNativeFileSystemStore.KEY_USE_SECURE_MODE; + +/** + * Test class to hold all WasbRemoteCallHelper tests + */ +public class TestWasbRemoteCallHelper + extends AbstractWasbTestBase { + + @Override + protected AzureBlobStorageTestAccount createTestAccount() throws Exception { + Configuration conf = new Configuration(); + conf.set(NativeAzureFileSystem.KEY_AZURE_AUTHORIZATION, "true"); + conf.set(RemoteWasbAuthorizerImpl.KEY_REMOTE_AUTH_SERVICE_URL, "http://localhost/"); + return AzureBlobStorageTestAccount.create(conf); + } + + @Before + public void beforeMethod() { + boolean useSecureMode = fs.getConf().getBoolean(KEY_USE_SECURE_MODE, false); + boolean useAuthorization = fs.getConf().getBoolean(NativeAzureFileSystem.KEY_AZURE_AUTHORIZATION, false); + Assume.assumeTrue("Test valid when both SecureMode and Authorization are enabled .. skipping", + useSecureMode && useAuthorization); + + Assume.assumeTrue( + useSecureMode && useAuthorization + ); + } + + @Rule + public ExpectedException expectedEx = ExpectedException.none(); + + /** + * Test invalid status-code + * @throws Throwable + */ + @Test // (expected = WasbAuthorizationException.class) + public void testInvalidStatusCode() throws Throwable { + + setupExpectations(); + + // set up mocks + HttpClient mockHttpClient = Mockito.mock(HttpClient.class); + HttpResponse mockHttpResponse = Mockito.mock(HttpResponse.class); + Mockito.when(mockHttpClient.execute(Mockito.any())).thenReturn(mockHttpResponse); + Mockito.when(mockHttpResponse.getStatusLine()).thenReturn(newStatusLine(999)); + // finished setting up mocks + + performop(mockHttpClient); + } + + /** + * Test invalid Content-Type + * @throws Throwable + */ + @Test // (expected = WasbAuthorizationException.class) + public void testInvalidContentType() throws Throwable { + + setupExpectations(); + + // set up mocks + HttpClient mockHttpClient = Mockito.mock(HttpClient.class); + HttpResponse mockHttpResponse = Mockito.mock(HttpResponse.class); + Mockito.when(mockHttpClient.execute(Mockito.any())).thenReturn(mockHttpResponse); + Mockito.when(mockHttpResponse.getStatusLine()).thenReturn(newStatusLine(200)); + Mockito.when(mockHttpResponse.getFirstHeader("Content-Type")) + .thenReturn(newHeader("Content-Type", "text/plain")); + // finished setting up mocks + + performop(mockHttpClient); + } + + /** + * Test missing Content-Length + * @throws Throwable + */ + @Test // (expected = WasbAuthorizationException.class) + public void testMissingContentLength() throws Throwable { + + setupExpectations(); + + // set up mocks + HttpClient mockHttpClient = Mockito.mock(HttpClient.class); + HttpResponse mockHttpResponse = Mockito.mock(HttpResponse.class); + Mockito.when(mockHttpClient.execute(Mockito.any())).thenReturn(mockHttpResponse); + Mockito.when(mockHttpResponse.getStatusLine()).thenReturn(newStatusLine(200)); + Mockito.when(mockHttpResponse.getFirstHeader("Content-Type")) + .thenReturn(newHeader("Content-Type", "application/json")); + // finished setting up mocks + + performop(mockHttpClient); + } + + /** + * Test Content-Length exceeds max + * @throws Throwable + */ + @Test // (expected = WasbAuthorizationException.class) + public void testContentLengthExceedsMax() throws Throwable { + + setupExpectations(); + + // set up mocks + HttpClient mockHttpClient = Mockito.mock(HttpClient.class); + HttpResponse mockHttpResponse = Mockito.mock(HttpResponse.class); + Mockito.when(mockHttpClient.execute(Mockito.any())).thenReturn(mockHttpResponse); + Mockito.when(mockHttpResponse.getStatusLine()).thenReturn(newStatusLine(200)); + Mockito.when(mockHttpResponse.getFirstHeader("Content-Type")) + .thenReturn(newHeader("Content-Type", "application/json")); + Mockito.when(mockHttpResponse.getFirstHeader("Content-Length")) + .thenReturn(newHeader("Content-Length", "2048")); + // finished setting up mocks + + performop(mockHttpClient); + } + + /** + * Test invalid Content-Length value + * @throws Throwable + */ + @Test // (expected = WasbAuthorizationException.class) + public void testInvalidContentLengthValue() throws Throwable { + + setupExpectations(); + + // set up mocks + HttpClient mockHttpClient = Mockito.mock(HttpClient.class); + HttpResponse mockHttpResponse = Mockito.mock(HttpResponse.class); + Mockito.when(mockHttpClient.execute(Mockito.any())).thenReturn(mockHttpResponse); + Mockito.when(mockHttpResponse.getStatusLine()).thenReturn(newStatusLine(200)); + Mockito.when(mockHttpResponse.getFirstHeader("Content-Type")) + .thenReturn(newHeader("Content-Type", "application/json")); + Mockito.when(mockHttpResponse.getFirstHeader("Content-Length")) + .thenReturn(newHeader("Content-Length", "20abc48")); + // finished setting up mocks + + performop(mockHttpClient); + } + + /** + * Test valid JSON response + * @throws Throwable + */ + @Test + public void testValidJSONResponse() throws Throwable { + + // set up mocks + HttpClient mockHttpClient = Mockito.mock(HttpClient.class); + + HttpResponse mockHttpResponse = Mockito.mock(HttpResponse.class); + HttpEntity mockHttpEntity = Mockito.mock(HttpEntity.class); + + Mockito.when(mockHttpClient.execute(Mockito.any())).thenReturn(mockHttpResponse); + Mockito.when(mockHttpResponse.getStatusLine()).thenReturn(newStatusLine(200)); + Mockito.when(mockHttpResponse.getFirstHeader("Content-Type")) + .thenReturn(newHeader("Content-Type", "application/json")); + Mockito.when(mockHttpResponse.getFirstHeader("Content-Length")) + .thenReturn(newHeader("Content-Length", "1024")); + Mockito.when(mockHttpResponse.getEntity()).thenReturn(mockHttpEntity); + Mockito.when(mockHttpEntity.getContent()) + .thenReturn(new ByteArrayInputStream(validJsonResponse().getBytes(StandardCharsets.UTF_8))) + .thenReturn(new ByteArrayInputStream(validJsonResponse().getBytes(StandardCharsets.UTF_8))) + .thenReturn(new ByteArrayInputStream(validJsonResponse().getBytes(StandardCharsets.UTF_8))); + // finished setting up mocks + + performop(mockHttpClient); + } + + /** + * Test malformed JSON response + * @throws Throwable + */ + @Test // (expected = WasbAuthorizationException.class) + public void testMalFormedJSONResponse() throws Throwable { + + expectedEx.expect(WasbAuthorizationException.class); + expectedEx.expectMessage("com.fasterxml.jackson.core.JsonParseException: Unexpected end-of-input in FIELD_NAME"); + + // set up mocks + HttpClient mockHttpClient = Mockito.mock(HttpClient.class); + + HttpResponse mockHttpResponse = Mockito.mock(HttpResponse.class); + HttpEntity mockHttpEntity = Mockito.mock(HttpEntity.class); + + Mockito.when(mockHttpClient.execute(Mockito.any())).thenReturn(mockHttpResponse); + Mockito.when(mockHttpResponse.getStatusLine()).thenReturn(newStatusLine(200)); + Mockito.when(mockHttpResponse.getFirstHeader("Content-Type")) + .thenReturn(newHeader("Content-Type", "application/json")); + Mockito.when(mockHttpResponse.getFirstHeader("Content-Length")) + .thenReturn(newHeader("Content-Length", "1024")); + Mockito.when(mockHttpResponse.getEntity()).thenReturn(mockHttpEntity); + Mockito.when(mockHttpEntity.getContent()) + .thenReturn(new ByteArrayInputStream(malformedJsonResponse().getBytes(StandardCharsets.UTF_8))); + // finished setting up mocks + + performop(mockHttpClient); + } + + /** + * Test valid JSON response failure response code + * @throws Throwable + */ + @Test // (expected = WasbAuthorizationException.class) + public void testFailureCodeJSONResponse() throws Throwable { + + expectedEx.expect(WasbAuthorizationException.class); + expectedEx.expectMessage("Remote authorization service encountered an error Unauthorized"); + + // set up mocks + HttpClient mockHttpClient = Mockito.mock(HttpClient.class); + + HttpResponse mockHttpResponse = Mockito.mock(HttpResponse.class); + HttpEntity mockHttpEntity = Mockito.mock(HttpEntity.class); + + Mockito.when(mockHttpClient.execute(Mockito.any())).thenReturn(mockHttpResponse); + Mockito.when(mockHttpResponse.getStatusLine()).thenReturn(newStatusLine(200)); + Mockito.when(mockHttpResponse.getFirstHeader("Content-Type")) + .thenReturn(newHeader("Content-Type", "application/json")); + Mockito.when(mockHttpResponse.getFirstHeader("Content-Length")) + .thenReturn(newHeader("Content-Length", "1024")); + Mockito.when(mockHttpResponse.getEntity()).thenReturn(mockHttpEntity); + Mockito.when(mockHttpEntity.getContent()) + .thenReturn(new ByteArrayInputStream(failureCodeJsonResponse().getBytes(StandardCharsets.UTF_8))); + // finished setting up mocks + + performop(mockHttpClient); + } + + private void setupExpectations() { + expectedEx.expect(WasbAuthorizationException.class); + expectedEx.expectMessage("org.apache.hadoop.fs.azure.WasbRemoteCallException: " + + "http://localhost/CHECK_AUTHORIZATION?wasb_absolute_path=%2Ftest.dat&" + + "operation_type=write&delegation_token:Encountered IOException while making remote call"); + } + + private void performop(HttpClient mockHttpClient) throws Throwable { + AzureBlobStorageTestAccount testAccount = createTestAccount(); + NativeAzureFileSystem fs = testAccount.getFileSystem(); + + Path testPath = new Path("/", "test.dat"); + + RemoteWasbAuthorizerImpl authorizer = new RemoteWasbAuthorizerImpl(); + authorizer.init(fs.getConf()); + WasbRemoteCallHelper mockWasbRemoteCallHelper = new WasbRemoteCallHelper(); + mockWasbRemoteCallHelper.updateHttpClient(mockHttpClient); + authorizer.updateWasbRemoteCallHelper(mockWasbRemoteCallHelper); + fs.updateWasbAuthorizer(authorizer); + + fs.create(testPath); + ContractTestUtils.assertPathExists(fs, "testPath was not created", testPath); + fs.delete(testPath, false); + } + + private String validJsonResponse() { + return new String( + "{\"responseCode\": 0, \"authorizationResult\": true, \"responseMessage\": \"Authorized\"}" + ); + } + + private String malformedJsonResponse() { + return new String( + "{\"responseCode\": 0, \"authorizationResult\": true, \"responseMessage\":" + ); + } + + private String failureCodeJsonResponse() { + return new String( + "{\"responseCode\": 1, \"authorizationResult\": false, \"responseMessage\": \"Unauthorized\"}" + ); + } + + private StatusLine newStatusLine(int statusCode) { + return new StatusLine() { + @Override + public ProtocolVersion getProtocolVersion() { + return new ProtocolVersion("HTTP", 1, 1); + } + + @Override + public int getStatusCode() { + return statusCode; + } + + @Override + public String getReasonPhrase() { + return "Reason Phrase"; + } + }; + } + + private Header newHeader(String name, String value) { + return new Header() { + @Override + public String getName() { + return name; + } + + @Override + public String getValue() { + return value; + } + + @Override + public HeaderElement[] getElements() throws ParseException { + return new HeaderElement[0]; + } + }; + } +} \ No newline at end of file diff --git a/hadoop-tools/hadoop-azure/src/test/resources/azure-test.xml b/hadoop-tools/hadoop-azure/src/test/resources/azure-test.xml index e898aa631e5..c73d6d84891 100644 --- a/hadoop-tools/hadoop-azure/src/test/resources/azure-test.xml +++ b/hadoop-tools/hadoop-azure/src/test/resources/azure-test.xml @@ -19,27 +19,21 @@ + fs.azure.secure.mode false - - fs.azure.local.sas.key.mode - false - - - fs.azure.cred.service.url - {CRED_SERIVCE_URL} - - --> + From 14413989cac9acc1fa6f8d330fac32f772613325 Mon Sep 17 00:00:00 2001 From: Akira Ajisaka Date: Tue, 7 Mar 2017 13:10:59 +0900 Subject: [PATCH 021/188] MAPREDUCE-6855. Specify charset when create String in CredentialsTestJob. Contributed by Kai Sasaki. --- .../apache/hadoop/mapreduce/security/CredentialsTestJob.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/java/org/apache/hadoop/mapreduce/security/CredentialsTestJob.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/java/org/apache/hadoop/mapreduce/security/CredentialsTestJob.java index e66fb2fbf3e..755e2dfba65 100644 --- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/java/org/apache/hadoop/mapreduce/security/CredentialsTestJob.java +++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/java/org/apache/hadoop/mapreduce/security/CredentialsTestJob.java @@ -19,6 +19,7 @@ package org.apache.hadoop.mapreduce.security; import java.io.IOException; +import java.nio.charset.StandardCharsets; import org.apache.hadoop.conf.Configuration; @@ -64,7 +65,7 @@ public class CredentialsTestJob extends Configured implements Tool { // fail the test } - String secretValueStr = new String (secretValue); + String secretValueStr = new String (secretValue, StandardCharsets.UTF_8); System.out.println(secretValueStr); if ( !("password"+i).equals(secretValueStr)){ From f01a69f84f4cc7d925d078a7ce32e5800da4e429 Mon Sep 17 00:00:00 2001 From: Akira Ajisaka Date: Tue, 7 Mar 2017 13:22:11 +0900 Subject: [PATCH 022/188] Treat encrypted files as private. Contributed by Daniel Templeton. --- .../ClientDistributedCacheManager.java | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/filecache/ClientDistributedCacheManager.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/filecache/ClientDistributedCacheManager.java index 73a0330396d..9f8edb5df0d 100644 --- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/filecache/ClientDistributedCacheManager.java +++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/filecache/ClientDistributedCacheManager.java @@ -294,10 +294,21 @@ public class ClientDistributedCacheManager { FsAction action, Map statCache) throws IOException { FileStatus status = getFileStatus(fs, path.toUri(), statCache); FsPermission perms = status.getPermission(); - FsAction otherAction = perms.getOtherAction(); - if (otherAction.implies(action)) { - return true; + + // Encrypted files are always treated as private. This stance has two + // important side effects. The first is that the encrypted files will be + // downloaded as the job owner instead of the YARN user, which is required + // for the KMS ACLs to work as expected. Second, it prevent a file with + // world readable permissions that is stored in an encryption zone from + // being localized as a publicly shared file with world readable + // permissions. + if (!perms.getEncryptedBit()) { + FsAction otherAction = perms.getOtherAction(); + if (otherAction.implies(action)) { + return true; + } } + return false; } From f597f4c43e0a6e2304b9bcaf727d6d8d15a365f9 Mon Sep 17 00:00:00 2001 From: Akira Ajisaka Date: Tue, 7 Mar 2017 15:14:55 +0900 Subject: [PATCH 023/188] HADOOP-14087. S3A typo in pom.xml test exclusions. Contributed by Aaron Fabbri. --- hadoop-tools/hadoop-aws/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hadoop-tools/hadoop-aws/pom.xml b/hadoop-tools/hadoop-aws/pom.xml index e5bbbfdffb9..c1880552b12 100644 --- a/hadoop-tools/hadoop-aws/pom.xml +++ b/hadoop-tools/hadoop-aws/pom.xml @@ -184,7 +184,7 @@ **/ITest*Root*.java **/ITestS3AFileContextStatistics.java **/ITestS3AEncryptionSSE*.java - **/ITestS3AHuge*.java + **/ITestS3AHuge*.java From 959940b0ab563b4e42bace44f1dc9a8babcaa889 Mon Sep 17 00:00:00 2001 From: Arpit Agarwal Date: Tue, 7 Mar 2017 10:12:35 -0800 Subject: [PATCH 024/188] HDFS-11477. Simplify file IO profiling configuration. Contributed by Hanisha Koneru. --- .../hadoop-common/src/site/markdown/Metrics.md | 7 ++++++- .../org/apache/hadoop/hdfs/DFSConfigKeys.java | 6 +----- .../apache/hadoop/hdfs/server/common/Util.java | 17 +++++++++++++++++ .../hadoop/hdfs/server/datanode/DNConf.java | 7 ++++--- .../hdfs/server/datanode/FileIoProvider.java | 2 +- .../server/datanode/ProfilingFileIoEvents.java | 11 +++++------ .../datanode/TestDataNodeVolumeMetrics.java | 4 ++-- .../hadoop/tools/TestHdfsConfigFields.java | 2 -- 8 files changed, 36 insertions(+), 20 deletions(-) diff --git a/hadoop-common-project/hadoop-common/src/site/markdown/Metrics.md b/hadoop-common-project/hadoop-common/src/site/markdown/Metrics.md index 7900692b9f5..a8bdbebdc7d 100644 --- a/hadoop-common-project/hadoop-common/src/site/markdown/Metrics.md +++ b/hadoop-common-project/hadoop-common/src/site/markdown/Metrics.md @@ -332,7 +332,12 @@ Each metrics record contains tags such as SessionId and Hostname as additional i FsVolume -------- -Per-volume metrics contain Datanode Volume IO related statistics. Per-volume metrics are off by default. They can be enbabled by setting `dfs.datanode.enable.fileio.profiling` to **true**, but enabling per-volume metrics may have a performance impact. Each metrics record contains tags such as Hostname as additional information along with metrics. +Per-volume metrics contain Datanode Volume IO related statistics. Per-volume +metrics are off by default. They can be enabled by setting `dfs.datanode +.fileio.profiling.sampling.fraction` to a fraction between 0.0 and 1.0. +Setting this value to 0.0 would mean profiling is not enabled. But enabling +per-volume metrics may have a performance impact. Each metrics record +contains tags such as Hostname as additional information along with metrics. | Name | Description | |:---- |:---- | diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/DFSConfigKeys.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/DFSConfigKeys.java index be20829b6a9..82d6073399a 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/DFSConfigKeys.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/DFSConfigKeys.java @@ -718,10 +718,6 @@ public class DFSConfigKeys extends CommonConfigurationKeys { public static final long DFS_EDIT_LOG_TRANSFER_RATE_DEFAULT = 0; //no throttling // Datanode File IO Stats - public static final String DFS_DATANODE_ENABLE_FILEIO_PROFILING_KEY = - "dfs.datanode.enable.fileio.profiling"; - public static final boolean DFS_DATANODE_ENABLE_FILEIO_PROFILING_DEFAULT = - false; public static final String DFS_DATANODE_ENABLE_FILEIO_FAULT_INJECTION_KEY = "dfs.datanode.enable.fileio.fault.injection"; public static final boolean @@ -730,7 +726,7 @@ public class DFSConfigKeys extends CommonConfigurationKeys { DFS_DATANODE_FILEIO_PROFILING_SAMPLING_FRACTION_KEY = "dfs.datanode.fileio.profiling.sampling.fraction"; public static final double - DFS_DATANODE_FILEIO_PROFILING_SAMPLING_FRACTION_DEAFULT = 1.0; + DFS_DATANODE_FILEIO_PROFILING_SAMPLING_FRACTION_DEFAULT = 0.0; //Keys with no defaults public static final String DFS_DATANODE_PLUGINS_KEY = "dfs.datanode.plugins"; diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/common/Util.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/common/Util.java index 9c67f0a2ed7..fdb09df9dae 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/common/Util.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/common/Util.java @@ -388,4 +388,21 @@ public final class Util { addrsList.removeAll(addrsToExclude); return addrsList; } + + public static boolean isDiskStatsEnabled(double fileIOSamplingFraction) { + final boolean isEnabled; + if (fileIOSamplingFraction < 0.000001) { + LOG.info(DFSConfigKeys + .DFS_DATANODE_FILEIO_PROFILING_SAMPLING_FRACTION_KEY + " set to " + + fileIOSamplingFraction + ". Disabling file IO profiling"); + isEnabled = false; + } else { + LOG.info(DFSConfigKeys + .DFS_DATANODE_FILEIO_PROFILING_SAMPLING_FRACTION_KEY + " set to " + + fileIOSamplingFraction + ". Enabling file IO profiling"); + isEnabled = true; + } + + return isEnabled; + } } diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/DNConf.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/DNConf.java index 3275ba8f77c..21ffccc2135 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/DNConf.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/DNConf.java @@ -63,6 +63,7 @@ import org.apache.hadoop.hdfs.client.HdfsClientConfigKeys; import org.apache.hadoop.hdfs.protocol.HdfsConstants; import org.apache.hadoop.hdfs.protocol.datatransfer.TrustedChannelResolver; import org.apache.hadoop.hdfs.protocol.datatransfer.sasl.DataTransferSaslUtil; +import org.apache.hadoop.hdfs.server.common.Util; import org.apache.hadoop.security.SaslPropertiesResolver; import java.util.concurrent.TimeUnit; @@ -174,9 +175,9 @@ public class DNConf { this.peerStatsEnabled = getConf().getBoolean( DFSConfigKeys.DFS_DATANODE_PEER_STATS_ENABLED_KEY, DFSConfigKeys.DFS_DATANODE_PEER_STATS_ENABLED_DEFAULT); - this.diskStatsEnabled = getConf().getBoolean( - DFSConfigKeys.DFS_DATANODE_ENABLE_FILEIO_PROFILING_KEY, - DFSConfigKeys.DFS_DATANODE_ENABLE_FILEIO_PROFILING_DEFAULT); + this.diskStatsEnabled = Util.isDiskStatsEnabled(getConf().getDouble( + DFSConfigKeys.DFS_DATANODE_FILEIO_PROFILING_SAMPLING_FRACTION_KEY, + DFSConfigKeys.DFS_DATANODE_FILEIO_PROFILING_SAMPLING_FRACTION_DEFAULT)); this.outliersReportIntervalMs = getConf().getTimeDuration( DFS_DATANODE_OUTLIERS_REPORT_INTERVAL_KEY, DFS_DATANODE_OUTLIERS_REPORT_INTERVAL_DEFAULT, diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/FileIoProvider.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/FileIoProvider.java index 9def2e131f7..5508e0bdd2c 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/FileIoProvider.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/FileIoProvider.java @@ -62,7 +62,7 @@ import static org.apache.hadoop.hdfs.server.datanode.FileIoProvider.OPERATION.*; * * Behavior can be injected into these events by enabling the * profiling and/or fault injection event hooks through - * {@link DFSConfigKeys#DFS_DATANODE_ENABLE_FILEIO_PROFILING_KEY} and + * {@link DFSConfigKeys#DFS_DATANODE_FILEIO_PROFILING_SAMPLING_FRACTION_KEY} and * {@link DFSConfigKeys#DFS_DATANODE_ENABLE_FILEIO_FAULT_INJECTION_KEY}. * These event hooks are disabled by default. * diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/ProfilingFileIoEvents.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/ProfilingFileIoEvents.java index 9af84fa17fe..35118b2171c 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/ProfilingFileIoEvents.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/ProfilingFileIoEvents.java @@ -23,6 +23,7 @@ import org.apache.commons.logging.LogFactory; import org.apache.hadoop.classification.InterfaceAudience; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hdfs.DFSConfigKeys; +import org.apache.hadoop.hdfs.server.common.Util; import org.apache.hadoop.hdfs.server.datanode.fsdataset.DataNodeVolumeMetrics; import org.apache.hadoop.hdfs.server.datanode.fsdataset.FsVolumeSpi; import org.apache.hadoop.util.Time; @@ -43,13 +44,11 @@ class ProfilingFileIoEvents { public ProfilingFileIoEvents(@Nullable Configuration conf) { if (conf != null) { - isEnabled = conf.getBoolean(DFSConfigKeys - .DFS_DATANODE_ENABLE_FILEIO_PROFILING_KEY, DFSConfigKeys - .DFS_DATANODE_ENABLE_FILEIO_PROFILING_DEFAULT); - double fileIOSamplingFraction = conf.getDouble(DFSConfigKeys - .DFS_DATANODE_FILEIO_PROFILING_SAMPLING_FRACTION_KEY, + double fileIOSamplingFraction = conf.getDouble( + DFSConfigKeys.DFS_DATANODE_FILEIO_PROFILING_SAMPLING_FRACTION_KEY, DFSConfigKeys - .DFS_DATANODE_FILEIO_PROFILING_SAMPLING_FRACTION_DEAFULT); + .DFS_DATANODE_FILEIO_PROFILING_SAMPLING_FRACTION_DEFAULT); + isEnabled = Util.isDiskStatsEnabled(fileIOSamplingFraction); if (fileIOSamplingFraction > 1) { LOG.warn(DFSConfigKeys .DFS_DATANODE_FILEIO_PROFILING_SAMPLING_FRACTION_KEY + diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/datanode/TestDataNodeVolumeMetrics.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/datanode/TestDataNodeVolumeMetrics.java index 6a8ac9c4f59..03e1deea96f 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/datanode/TestDataNodeVolumeMetrics.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/datanode/TestDataNodeVolumeMetrics.java @@ -121,8 +121,8 @@ public class TestDataNodeVolumeMetrics { private MiniDFSCluster setupClusterForVolumeMetrics() throws IOException { Configuration conf = new HdfsConfiguration(); - conf.setBoolean(DFSConfigKeys - .DFS_DATANODE_ENABLE_FILEIO_PROFILING_KEY, true); + conf.setDouble(DFSConfigKeys + .DFS_DATANODE_FILEIO_PROFILING_SAMPLING_FRACTION_KEY, 1.0); SimulatedFSDataset.setFactory(conf); return new MiniDFSCluster.Builder(conf) .numDataNodes(NUM_DATANODES) diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/tools/TestHdfsConfigFields.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/tools/TestHdfsConfigFields.java index cdce3428e90..1fdf713562b 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/tools/TestHdfsConfigFields.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/tools/TestHdfsConfigFields.java @@ -103,8 +103,6 @@ public class TestHdfsConfigFields extends TestConfigurationFieldsBase { .add(DFSConfigKeys.DFS_DATANODE_STARTUP_KEY); configurationPropsToSkipCompare .add(DFSConfigKeys.DFS_NAMENODE_STARTUP_KEY); - configurationPropsToSkipCompare - .add(DFSConfigKeys.DFS_DATANODE_ENABLE_FILEIO_PROFILING_KEY); configurationPropsToSkipCompare.add(DFSConfigKeys .DFS_DATANODE_ENABLE_FILEIO_FAULT_INJECTION_KEY); configurationPropsToSkipCompare.add(DFSConfigKeys From 1f9848dfe1fc9148cbbcfcc3dfed948b9e0f3c3c Mon Sep 17 00:00:00 2001 From: Arpit Agarwal Date: Tue, 7 Mar 2017 11:41:05 -0800 Subject: [PATCH 025/188] HDFS-11508. Fix bind failure in SimpleTCPServer & Portmap where bind fails because socket is in TIME_WAIT state. Contributed by Mukul Kumar Singh. --- .../src/main/java/org/apache/hadoop/oncrpc/SimpleTcpServer.java | 1 + .../src/main/java/org/apache/hadoop/portmap/Portmap.java | 2 ++ 2 files changed, 3 insertions(+) diff --git a/hadoop-common-project/hadoop-nfs/src/main/java/org/apache/hadoop/oncrpc/SimpleTcpServer.java b/hadoop-common-project/hadoop-nfs/src/main/java/org/apache/hadoop/oncrpc/SimpleTcpServer.java index 99d1d6f0830..f7ab52e3d50 100644 --- a/hadoop-common-project/hadoop-nfs/src/main/java/org/apache/hadoop/oncrpc/SimpleTcpServer.java +++ b/hadoop-common-project/hadoop-nfs/src/main/java/org/apache/hadoop/oncrpc/SimpleTcpServer.java @@ -81,6 +81,7 @@ public class SimpleTcpServer { }); server.setOption("child.tcpNoDelay", true); server.setOption("child.keepAlive", true); + server.setOption("reuseAddress", true); // Listen to TCP port ch = server.bind(new InetSocketAddress(port)); diff --git a/hadoop-common-project/hadoop-nfs/src/main/java/org/apache/hadoop/portmap/Portmap.java b/hadoop-common-project/hadoop-nfs/src/main/java/org/apache/hadoop/portmap/Portmap.java index 2b887914f99..94d76d08fdd 100644 --- a/hadoop-common-project/hadoop-nfs/src/main/java/org/apache/hadoop/portmap/Portmap.java +++ b/hadoop-common-project/hadoop-nfs/src/main/java/org/apache/hadoop/portmap/Portmap.java @@ -109,12 +109,14 @@ final class Portmap { RpcUtil.STAGE_RPC_TCP_RESPONSE); } }); + tcpServer.setOption("reuseAddress", true); udpServer = new ConnectionlessBootstrap(new NioDatagramChannelFactory( Executors.newCachedThreadPool())); udpServer.setPipeline(Channels.pipeline(RpcUtil.STAGE_RPC_MESSAGE_PARSER, handler, RpcUtil.STAGE_RPC_UDP_RESPONSE)); + udpServer.setOption("reuseAddress", true); tcpChannel = tcpServer.bind(tcpAddress); udpChannel = udpServer.bind(udpAddress); From e0c239cdbda336e09a35d112d451c2e17d74a3fc Mon Sep 17 00:00:00 2001 From: Daniel Templeton Date: Tue, 7 Mar 2017 11:58:48 -0800 Subject: [PATCH 026/188] YARN-6287. RMCriticalThreadUncaughtExceptionHandler.rmContext should be final (Contributed by Corey Barker via Daniel Templeton) --- .../RMCriticalThreadUncaughtExceptionHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/RMCriticalThreadUncaughtExceptionHandler.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/RMCriticalThreadUncaughtExceptionHandler.java index c5c60876a30..a67f81a8f7f 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/RMCriticalThreadUncaughtExceptionHandler.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/RMCriticalThreadUncaughtExceptionHandler.java @@ -37,7 +37,7 @@ public class RMCriticalThreadUncaughtExceptionHandler implements UncaughtExceptionHandler { private static final Log LOG = LogFactory.getLog( RMCriticalThreadUncaughtExceptionHandler.class); - private RMContext rmContext; + private final RMContext rmContext; public RMCriticalThreadUncaughtExceptionHandler(RMContext rmContext) { this.rmContext = rmContext; From 38d75dfd3a643f8a1acd52e025a466d65065b60e Mon Sep 17 00:00:00 2001 From: Robert Kanter Date: Tue, 7 Mar 2017 13:34:46 -0800 Subject: [PATCH 027/188] MAPREDUCE-6839. TestRecovery.testCrashed failed (pairg via rkanter) --- .../apache/hadoop/mapreduce/v2/app/TestRecovery.java | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/test/java/org/apache/hadoop/mapreduce/v2/app/TestRecovery.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/test/java/org/apache/hadoop/mapreduce/v2/app/TestRecovery.java index 071575a156a..6332c5d825e 100644 --- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/test/java/org/apache/hadoop/mapreduce/v2/app/TestRecovery.java +++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/test/java/org/apache/hadoop/mapreduce/v2/app/TestRecovery.java @@ -159,9 +159,7 @@ public class TestRecovery { app.waitForState(task1Attempt1, TaskAttemptState.RUNNING); app.waitForState(task2Attempt, TaskAttemptState.RUNNING); - // reduces must be in NEW state - Assert.assertEquals("Reduce Task state not correct", - TaskState.RUNNING, reduceTask.getReport().getTaskState()); + app.waitForState(reduceTask, TaskState.RUNNING); /////////// Play some games with the TaskAttempts of the first task ////// //send the fail signal to the 1st map task attempt @@ -1301,9 +1299,7 @@ public class TestRecovery { app.waitForState(task1Attempt2, TaskAttemptState.RUNNING); app.waitForState(task2Attempt, TaskAttemptState.RUNNING); - // reduces must be in NEW state - Assert.assertEquals("Reduce Task state not correct", - TaskState.RUNNING, reduceTask.getReport().getTaskState()); + app.waitForState(reduceTask, TaskState.RUNNING); //send the done signal to the map 1 attempt 1 app.getContext().getEventHandler().handle( @@ -1431,9 +1427,7 @@ public class TestRecovery { app.waitForState(task1Attempt, TaskAttemptState.RUNNING); app.waitForState(task2Attempt, TaskAttemptState.RUNNING); - // reduces must be in NEW state - Assert.assertEquals("Reduce Task state not correct", - TaskState.RUNNING, reduceTask.getReport().getTaskState()); + app.waitForState(reduceTask, TaskState.RUNNING); //send the done signal to the 1st map attempt app.getContext().getEventHandler().handle( From 1598fd3b7948b3592775e3be3227c4a336122bc9 Mon Sep 17 00:00:00 2001 From: Robert Kanter Date: Tue, 7 Mar 2017 13:47:52 -0800 Subject: [PATCH 028/188] YARN-6275. Fail to show real-time tracking charts in SLS (yufeigu via rkanter) --- hadoop-tools/hadoop-sls/src/main/bin/slsrun.sh | 8 ++++++-- .../java/org/apache/hadoop/yarn/sls/web/SLSWebApp.java | 2 ++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/hadoop-tools/hadoop-sls/src/main/bin/slsrun.sh b/hadoop-tools/hadoop-sls/src/main/bin/slsrun.sh index 19b5c345321..fb53045ed37 100644 --- a/hadoop-tools/hadoop-sls/src/main/bin/slsrun.sh +++ b/hadoop-tools/hadoop-sls/src/main/bin/slsrun.sh @@ -103,12 +103,16 @@ function run_simulation() { hadoop_java_exec sls org.apache.hadoop.yarn.sls.SLSRunner ${args} } +this="${BASH_SOURCE-$0}" +bin=$(cd -P -- "$(dirname -- "${this}")" >/dev/null && pwd -P) + +# copy 'html' directory to current directory to make sure web sever can access +cp -r "${bin}/../html" "$(pwd)" + # let's locate libexec... if [[ -n "${HADOOP_HOME}" ]]; then HADOOP_DEFAULT_LIBEXEC_DIR="${HADOOP_HOME}/libexec" else - this="${BASH_SOURCE-$0}" - bin=$(cd -P -- "$(dirname -- "${this}")" >/dev/null && pwd -P) HADOOP_DEFAULT_LIBEXEC_DIR="${bin}/../../../../../libexec" fi diff --git a/hadoop-tools/hadoop-sls/src/main/java/org/apache/hadoop/yarn/sls/web/SLSWebApp.java b/hadoop-tools/hadoop-sls/src/main/java/org/apache/hadoop/yarn/sls/web/SLSWebApp.java index abdf1060afc..33d48466c20 100644 --- a/hadoop-tools/hadoop-sls/src/main/java/org/apache/hadoop/yarn/sls/web/SLSWebApp.java +++ b/hadoop-tools/hadoop-sls/src/main/java/org/apache/hadoop/yarn/sls/web/SLSWebApp.java @@ -39,6 +39,7 @@ import org.apache.hadoop.yarn.sls.SLSRunner; import org.apache.hadoop.yarn.sls.scheduler.FairSchedulerMetrics; import org.apache.hadoop.yarn.sls.scheduler.SchedulerMetrics; import org.apache.hadoop.yarn.sls.scheduler.SchedulerWrapper; +import org.eclipse.jetty.http.MimeTypes; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Server; @@ -118,6 +119,7 @@ public class SLSWebApp extends HttpServlet { public void start() throws Exception { // static files final ResourceHandler staticHandler = new ResourceHandler(); + staticHandler.setMimeTypes(new MimeTypes()); staticHandler.setResourceBase("html"); Handler handler = new AbstractHandler() { From 28daaf0eb206d723d2baf0f9d91e43d98bb2fd26 Mon Sep 17 00:00:00 2001 From: Mingliang Liu Date: Tue, 7 Mar 2017 14:55:52 -0800 Subject: [PATCH 029/188] HADOOP-14150. Implement getHomeDirectory() method in NativeAzureFileSystem. Contributed by Santhosh G Nayak --- .../apache/hadoop/fs/azure/NativeAzureFileSystem.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/NativeAzureFileSystem.java b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/NativeAzureFileSystem.java index 6de0a285a84..9aebbb5a22e 100644 --- a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/NativeAzureFileSystem.java +++ b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/NativeAzureFileSystem.java @@ -84,6 +84,7 @@ import com.microsoft.azure.storage.StorageException; @InterfaceStability.Stable public class NativeAzureFileSystem extends FileSystem { private static final int USER_WX_PERMISION = 0300; + private static final String USER_HOME_DIR_PREFIX_DEFAULT = "/user"; /** * A description of a folder rename operation, including the source and * destination keys, and descriptions of the files in the source folder. @@ -1129,6 +1130,8 @@ public class NativeAzureFileSystem extends FileSystem { */ private WasbAuthorizerInterface authorizer = null; + private UserGroupInformation ugi; + private String delegationToken = null; public NativeAzureFileSystem() { @@ -1247,6 +1250,7 @@ public class NativeAzureFileSystem extends FileSystem { store.initialize(uri, conf, instrumentation); setConf(conf); + this.ugi = UserGroupInformation.getCurrentUser(); this.uri = URI.create(uri.getScheme() + "://" + uri.getAuthority()); this.workingDir = new Path("/user", UserGroupInformation.getCurrentUser() .getShortUserName()).makeQualified(getUri(), getWorkingDirectory()); @@ -1276,6 +1280,12 @@ public class NativeAzureFileSystem extends FileSystem { } } + @Override + public Path getHomeDirectory() { + return makeQualified(new Path( + USER_HOME_DIR_PREFIX_DEFAULT + "/" + this.ugi.getShortUserName())); + } + @VisibleForTesting public void updateWasbAuthorizer(WasbAuthorizerInterface authorizer) { this.authorizer = authorizer; From 1eb81867032b016a59662043cbae50daa52dafa9 Mon Sep 17 00:00:00 2001 From: Sunil G Date: Wed, 8 Mar 2017 12:04:30 +0530 Subject: [PATCH 030/188] YARN-6207. Move application across queues should handle delayed event processing. Contributed by Bibin A Chundatt. --- .../SchedulerApplicationAttempt.java | 5 +- .../scheduler/capacity/CapacityScheduler.java | 69 +++--- .../capacity/TestCapacityScheduler.java | 200 ++++++++++++++++++ 3 files changed, 248 insertions(+), 26 deletions(-) diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/SchedulerApplicationAttempt.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/SchedulerApplicationAttempt.java index f894a40f09b..91e29d59ab7 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/SchedulerApplicationAttempt.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/SchedulerApplicationAttempt.java @@ -1069,6 +1069,7 @@ public class SchedulerApplicationAttempt implements SchedulableEntity { QueueMetrics newMetrics = newQueue.getMetrics(); String newQueueName = newQueue.getQueueName(); String user = getUser(); + for (RMContainer liveContainer : liveContainers.values()) { Resource resource = liveContainer.getContainer().getResource(); ((RMContainerImpl) liveContainer).setQueueName(newQueueName); @@ -1084,7 +1085,9 @@ public class SchedulerApplicationAttempt implements SchedulableEntity { } } - appSchedulingInfo.move(newQueue); + if (!isStopped) { + appSchedulingInfo.move(newQueue); + } this.queue = newQueue; } finally { writeLock.unlock(); diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/CapacityScheduler.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/CapacityScheduler.java index 20ea607cd4d..f6e79426865 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/CapacityScheduler.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/CapacityScheduler.java @@ -1939,36 +1939,47 @@ public class CapacityScheduler extends String targetQueueName) throws YarnException { try { writeLock.lock(); - FiCaSchedulerApp app = getApplicationAttempt( - ApplicationAttemptId.newInstance(appId, 0)); - String sourceQueueName = app.getQueue().getQueueName(); - LeafQueue source = this.queueManager.getAndCheckLeafQueue( - sourceQueueName); + SchedulerApplication application = + applications.get(appId); + if (application == null) { + throw new YarnException("App to be moved " + appId + " not found."); + } + String sourceQueueName = application.getQueue().getQueueName(); + LeafQueue source = + this.queueManager.getAndCheckLeafQueue(sourceQueueName); String destQueueName = handleMoveToPlanQueue(targetQueueName); LeafQueue dest = this.queueManager.getAndCheckLeafQueue(destQueueName); - String user = app.getUser(); + String user = application.getUser(); try { dest.submitApplication(appId, user, destQueueName); } catch (AccessControlException e) { throw new YarnException(e); } - // Move all live containers - for (RMContainer rmContainer : app.getLiveContainers()) { - source.detachContainer(getClusterResource(), app, rmContainer); - // attach the Container to another queue - dest.attachContainer(getClusterResource(), app, rmContainer); + + FiCaSchedulerApp app = application.getCurrentAppAttempt(); + if (app != null) { + // Move all live containers even when stopped. + // For transferStateFromPreviousAttempt required + for (RMContainer rmContainer : app.getLiveContainers()) { + source.detachContainer(getClusterResource(), app, rmContainer); + // attach the Container to another queue + dest.attachContainer(getClusterResource(), app, rmContainer); + } + if (!app.isStopped()) { + source.finishApplicationAttempt(app, sourceQueueName); + // Submit to a new queue + dest.submitApplicationAttempt(app, user); + } + // Finish app & update metrics + app.move(dest); } + source.appFinished(); // Detach the application.. - source.finishApplicationAttempt(app, sourceQueueName); - source.getParent().finishApplication(appId, app.getUser()); - // Finish app & update metrics - app.move(dest); - // Submit to a new queue - dest.submitApplicationAttempt(app, user); - applications.get(appId).setQueue(dest); - LOG.info("App: " + app.getApplicationId() + " successfully moved from " - + sourceQueueName + " to: " + destQueueName); + source.getParent().finishApplication(appId, user); + application.setQueue(dest); + LOG.info("App: " + appId + " successfully moved from " + sourceQueueName + + " to: " + destQueueName); return targetQueueName; } finally { writeLock.unlock(); @@ -1980,15 +1991,23 @@ public class CapacityScheduler extends String newQueue) throws YarnException { try { writeLock.lock(); - FiCaSchedulerApp app = getApplicationAttempt( - ApplicationAttemptId.newInstance(appId, 0)); - String sourceQueueName = app.getQueue().getQueueName(); + SchedulerApplication application = + applications.get(appId); + if (application == null) { + throw new YarnException("App to be moved " + appId + " not found."); + } + String sourceQueueName = application.getQueue().getQueueName(); this.queueManager.getAndCheckLeafQueue(sourceQueueName); String destQueueName = handleMoveToPlanQueue(newQueue); LeafQueue dest = this.queueManager.getAndCheckLeafQueue(destQueueName); // Validation check - ACLs, submission limits for user & queue - String user = app.getUser(); - checkQueuePartition(app, dest); + String user = application.getUser(); + // Check active partition only when attempt is available + FiCaSchedulerApp appAttempt = + getApplicationAttempt(ApplicationAttemptId.newInstance(appId, 0)); + if (null != appAttempt) { + checkQueuePartition(appAttempt, dest); + } try { dest.validateSubmitApplication(appId, user, destQueueName); } catch (AccessControlException e) { diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/TestCapacityScheduler.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/TestCapacityScheduler.java index 2b60ecfa6c3..293bac21174 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/TestCapacityScheduler.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/TestCapacityScheduler.java @@ -62,6 +62,7 @@ import org.apache.hadoop.yarn.api.records.ContainerId; import org.apache.hadoop.yarn.api.records.ContainerState; import org.apache.hadoop.yarn.api.records.ContainerStatus; import org.apache.hadoop.yarn.api.records.ContainerUpdateType; +import org.apache.hadoop.yarn.api.records.ExecutionType; import org.apache.hadoop.yarn.api.records.NodeId; import org.apache.hadoop.yarn.api.records.NodeState; import org.apache.hadoop.yarn.api.records.Priority; @@ -110,12 +111,14 @@ import org.apache.hadoop.yarn.server.resourcemanager.rmapp.attempt.RMAppAttemptS import org.apache.hadoop.yarn.server.resourcemanager.rmcontainer.RMContainer; import org.apache.hadoop.yarn.server.resourcemanager.rmcontainer.RMContainerEvent; import org.apache.hadoop.yarn.server.resourcemanager.rmcontainer.RMContainerEventType; +import org.apache.hadoop.yarn.server.resourcemanager.rmcontainer.RMContainerImpl; import org.apache.hadoop.yarn.server.resourcemanager.rmcontainer.RMContainerState; import org.apache.hadoop.yarn.server.resourcemanager.rmnode.RMNode; import org.apache.hadoop.yarn.server.resourcemanager.rmnode.RMNodeResourceUpdateEvent; import org.apache.hadoop.yarn.server.resourcemanager.scheduler.AbstractYarnScheduler; import org.apache.hadoop.yarn.server.resourcemanager.scheduler.Allocation; import org.apache.hadoop.yarn.server.resourcemanager.scheduler.ContainerUpdates; +import org.apache.hadoop.yarn.server.resourcemanager.scheduler.QueueMetrics; import org.apache.hadoop.yarn.server.resourcemanager.scheduler.ResourceScheduler; import org.apache.hadoop.yarn.server.resourcemanager.scheduler.SchedulerApplication; import org.apache.hadoop.yarn.server.resourcemanager.scheduler.SchedulerApplicationAttempt; @@ -2209,6 +2212,203 @@ public class TestCapacityScheduler { rm.stop(); } + @Test(timeout = 60000) + public void testMoveAttemptNotAdded() throws Exception { + Configuration conf = new Configuration(); + conf.setClass(YarnConfiguration.RM_SCHEDULER, CapacityScheduler.class, + ResourceScheduler.class); + MockRM rm = new MockRM(getCapacityConfiguration(conf)); + rm.start(); + CapacityScheduler cs = (CapacityScheduler) rm.getResourceScheduler(); + + ApplicationId appId = BuilderUtils.newApplicationId(100, 1); + ApplicationAttemptId appAttemptId = + BuilderUtils.newApplicationAttemptId(appId, 1); + + RMAppAttemptMetrics attemptMetric = + new RMAppAttemptMetrics(appAttemptId, rm.getRMContext()); + RMAppImpl app = mock(RMAppImpl.class); + when(app.getApplicationId()).thenReturn(appId); + RMAppAttemptImpl attempt = mock(RMAppAttemptImpl.class); + Container container = mock(Container.class); + when(attempt.getMasterContainer()).thenReturn(container); + ApplicationSubmissionContext submissionContext = + mock(ApplicationSubmissionContext.class); + when(attempt.getSubmissionContext()).thenReturn(submissionContext); + when(attempt.getAppAttemptId()).thenReturn(appAttemptId); + when(attempt.getRMAppAttemptMetrics()).thenReturn(attemptMetric); + when(app.getCurrentAppAttempt()).thenReturn(attempt); + + rm.getRMContext().getRMApps().put(appId, app); + + SchedulerEvent addAppEvent = + new AppAddedSchedulerEvent(appId, "a1", "user"); + try { + cs.moveApplication(appId, "b1"); + fail("Move should throw exception app not available"); + } catch (YarnException e) { + assertEquals("App to be moved application_100_0001 not found.", + e.getMessage()); + } + cs.handle(addAppEvent); + cs.moveApplication(appId, "b1"); + SchedulerEvent addAttemptEvent = + new AppAttemptAddedSchedulerEvent(appAttemptId, false); + cs.handle(addAttemptEvent); + CSQueue rootQ = cs.getRootQueue(); + CSQueue queueB = cs.getQueue("b"); + CSQueue queueA = cs.getQueue("a"); + CSQueue queueA1 = cs.getQueue("a1"); + CSQueue queueB1 = cs.getQueue("b1"); + Assert.assertEquals(1, rootQ.getNumApplications()); + Assert.assertEquals(0, queueA.getNumApplications()); + Assert.assertEquals(1, queueB.getNumApplications()); + Assert.assertEquals(0, queueA1.getNumApplications()); + Assert.assertEquals(1, queueB1.getNumApplications()); + + rm.close(); + } + + @Test + public void testRemoveAttemptMoveAdded() throws Exception { + YarnConfiguration conf = new YarnConfiguration(); + conf.setClass(YarnConfiguration.RM_SCHEDULER, CapacityScheduler.class, + CapacityScheduler.class); + conf.setInt(YarnConfiguration.RM_AM_MAX_ATTEMPTS, 2); + // Create Mock RM + MockRM rm = new MockRM(getCapacityConfiguration(conf)); + CapacityScheduler sch = (CapacityScheduler) rm.getResourceScheduler(); + // add node + Resource newResource = Resource.newInstance(4 * GB, 1); + RMNode node = MockNodes.newNodeInfo(0, newResource, 1, "127.0.0.1"); + SchedulerEvent addNode = new NodeAddedSchedulerEvent(node); + sch.handle(addNode); + // create appid + ApplicationId appId = BuilderUtils.newApplicationId(100, 1); + ApplicationAttemptId appAttemptId = + BuilderUtils.newApplicationAttemptId(appId, 1); + + RMAppAttemptMetrics attemptMetric = + new RMAppAttemptMetrics(appAttemptId, rm.getRMContext()); + RMAppImpl app = mock(RMAppImpl.class); + when(app.getApplicationId()).thenReturn(appId); + RMAppAttemptImpl attempt = mock(RMAppAttemptImpl.class); + Container container = mock(Container.class); + when(attempt.getMasterContainer()).thenReturn(container); + ApplicationSubmissionContext submissionContext = + mock(ApplicationSubmissionContext.class); + when(attempt.getSubmissionContext()).thenReturn(submissionContext); + when(attempt.getAppAttemptId()).thenReturn(appAttemptId); + when(attempt.getRMAppAttemptMetrics()).thenReturn(attemptMetric); + when(app.getCurrentAppAttempt()).thenReturn(attempt); + + rm.getRMContext().getRMApps().put(appId, app); + // Add application + SchedulerEvent addAppEvent = + new AppAddedSchedulerEvent(appId, "a1", "user"); + sch.handle(addAppEvent); + // Add application attempt + SchedulerEvent addAttemptEvent = + new AppAttemptAddedSchedulerEvent(appAttemptId, false); + sch.handle(addAttemptEvent); + // get Queues + CSQueue queueA1 = sch.getQueue("a1"); + CSQueue queueB = sch.getQueue("b"); + CSQueue queueB1 = sch.getQueue("b1"); + + // add Running rm container and simulate live containers to a1 + ContainerId newContainerId = ContainerId.newContainerId(appAttemptId, 2); + RMContainerImpl rmContainer = mock(RMContainerImpl.class); + when(rmContainer.getState()).thenReturn(RMContainerState.RUNNING); + Container container2 = mock(Container.class); + when(rmContainer.getContainer()).thenReturn(container2); + Resource resource = Resource.newInstance(1024, 1); + when(container2.getResource()).thenReturn(resource); + when(rmContainer.getExecutionType()).thenReturn(ExecutionType.GUARANTEED); + when(container2.getNodeId()).thenReturn(node.getNodeID()); + when(container2.getId()).thenReturn(newContainerId); + when(rmContainer.getNodeLabelExpression()) + .thenReturn(RMNodeLabelsManager.NO_LABEL); + when(rmContainer.getContainerId()).thenReturn(newContainerId); + sch.getApplicationAttempt(appAttemptId).getLiveContainersMap() + .put(newContainerId, rmContainer); + QueueMetrics queueA1M = queueA1.getMetrics(); + queueA1M.incrPendingResources("user1", 1, resource); + queueA1M.allocateResources("user1", resource); + // remove attempt + sch.handle(new AppAttemptRemovedSchedulerEvent(appAttemptId, + RMAppAttemptState.KILLED, true)); + // Move application to queue b1 + sch.moveApplication(appId, "b1"); + // Check queue metrics after move + Assert.assertEquals(0, queueA1.getNumApplications()); + Assert.assertEquals(1, queueB.getNumApplications()); + Assert.assertEquals(0, queueB1.getNumApplications()); + + // Release attempt add event + ApplicationAttemptId appAttemptId2 = + BuilderUtils.newApplicationAttemptId(appId, 2); + SchedulerEvent addAttemptEvent2 = + new AppAttemptAddedSchedulerEvent(appAttemptId2, true); + sch.handle(addAttemptEvent2); + + // Check metrics after attempt added + Assert.assertEquals(0, queueA1.getNumApplications()); + Assert.assertEquals(1, queueB.getNumApplications()); + Assert.assertEquals(1, queueB1.getNumApplications()); + + + QueueMetrics queueB1M = queueB1.getMetrics(); + QueueMetrics queueBM = queueB.getMetrics(); + // Verify allocation MB of current state + Assert.assertEquals(0, queueA1M.getAllocatedMB()); + Assert.assertEquals(0, queueA1M.getAllocatedVirtualCores()); + Assert.assertEquals(1024, queueB1M.getAllocatedMB()); + Assert.assertEquals(1, queueB1M.getAllocatedVirtualCores()); + + // remove attempt + sch.handle(new AppAttemptRemovedSchedulerEvent(appAttemptId2, + RMAppAttemptState.FINISHED, false)); + + Assert.assertEquals(0, queueA1M.getAllocatedMB()); + Assert.assertEquals(0, queueA1M.getAllocatedVirtualCores()); + Assert.assertEquals(0, queueB1M.getAllocatedMB()); + Assert.assertEquals(0, queueB1M.getAllocatedVirtualCores()); + + verifyQueueMetrics(queueB1M); + verifyQueueMetrics(queueBM); + // Verify queue A1 metrics + verifyQueueMetrics(queueA1M); + rm.close(); + } + + private void verifyQueueMetrics(QueueMetrics queue) { + Assert.assertEquals(0, queue.getPendingMB()); + Assert.assertEquals(0, queue.getActiveUsers()); + Assert.assertEquals(0, queue.getActiveApps()); + Assert.assertEquals(0, queue.getAppsPending()); + Assert.assertEquals(0, queue.getAppsRunning()); + Assert.assertEquals(0, queue.getAllocatedMB()); + Assert.assertEquals(0, queue.getAllocatedVirtualCores()); + } + + private Configuration getCapacityConfiguration(Configuration config) { + CapacitySchedulerConfiguration conf = + new CapacitySchedulerConfiguration(config); + + // Define top-level queues + conf.setQueues(CapacitySchedulerConfiguration.ROOT, + new String[] {"a", "b"}); + conf.setCapacity(A, 50); + conf.setCapacity(B, 50); + conf.setQueues(A, new String[] {"a1", "a2"}); + conf.setCapacity(A1, 50); + conf.setCapacity(A2, 50); + conf.setQueues(B, new String[] {"b1"}); + conf.setCapacity(B1, 100); + return conf; + } + @Test public void testKillAllAppsInQueue() throws Exception { MockRM rm = setUpMove(); From 4ebe8a6a237258de9a7d8b041d78249bd3cca7a6 Mon Sep 17 00:00:00 2001 From: Jason Lowe Date: Wed, 8 Mar 2017 10:27:57 -0600 Subject: [PATCH 031/188] MAPREDUCE-6859. hadoop-mapreduce-client-jobclient.jar sets a main class that isn't in the JAR. Contributed by Daniel Templeton --- .../hadoop-mapreduce-client-jobclient/pom.xml | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/pom.xml b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/pom.xml index 5cecebbf693..1747f5989d9 100644 --- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/pom.xml +++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/pom.xml @@ -143,17 +143,15 @@ **/hdfs-site.xml + + + org.apache.hadoop.test.MapredTestDriver + + test-compile - - - - org.apache.hadoop.test.MapredTestDriver - - - org.apache.maven.plugins From 5addacb1e301991a8285a221c726f66330cd6d08 Mon Sep 17 00:00:00 2001 From: Andrew Wang Date: Wed, 8 Mar 2017 08:47:38 -0800 Subject: [PATCH 032/188] HDFS-11152. Start erasure coding policy ID number from 1 instead of 0 to void potential unexpected errors. Contributed by SammiChen. --- .../org/apache/hadoop/hdfs/protocol/HdfsConstants.java | 10 +++++----- .../hdfs/server/namenode/FSImageFormatPBINode.java | 3 +++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/protocol/HdfsConstants.java b/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/protocol/HdfsConstants.java index a9f18392450..d2209a49952 100644 --- a/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/protocol/HdfsConstants.java +++ b/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/protocol/HdfsConstants.java @@ -144,11 +144,11 @@ public final class HdfsConstants { ALL, LIVE, DEAD, DECOMMISSIONING, ENTERING_MAINTENANCE } - public static final byte RS_6_3_POLICY_ID = 0; - public static final byte RS_3_2_POLICY_ID = 1; - public static final byte RS_6_3_LEGACY_POLICY_ID = 2; - public static final byte XOR_2_1_POLICY_ID = 3; - public static final byte RS_10_4_POLICY_ID = 4; + public static final byte RS_6_3_POLICY_ID = 1; + public static final byte RS_3_2_POLICY_ID = 2; + public static final byte RS_6_3_LEGACY_POLICY_ID = 3; + public static final byte XOR_2_1_POLICY_ID = 4; + public static final byte RS_10_4_POLICY_ID = 5; /* Hidden constructor */ protected HdfsConstants() { 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 index 0ceae7816d7..17b1da7422f 100644 --- 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 @@ -342,6 +342,9 @@ public final class FSImageFormatPBINode { for (int i = 0; i < bp.size(); ++i) { BlockProto b = bp.get(i); if (isStriped) { + Preconditions.checkState(ecPolicy.getId() > 0, + "File with ID " + n.getId() + + " has an invalid erasure coding policy ID " + ecPolicy.getId()); blocks[i] = new BlockInfoStriped(PBHelperClient.convert(b), ecPolicy); } else { blocks[i] = new BlockInfoContiguous(PBHelperClient.convert(b), From 98142d2f722e82d57b0e2bae6276f7c17fd99598 Mon Sep 17 00:00:00 2001 From: John Zhuge Date: Mon, 6 Mar 2017 11:14:33 -0800 Subject: [PATCH 033/188] Revert "HADOOP-13606 swift FS to add a service load metadata file. Contributed by Steve Loughran" This reverts commit 53a12fa721bb431f7d481aac7d245c93efb56153. --- .../src/main/resources/core-default.xml | 6 ++++++ .../services/org.apache.hadoop.fs.FileSystem | 16 ---------------- 2 files changed, 6 insertions(+), 16 deletions(-) delete mode 100644 hadoop-tools/hadoop-openstack/src/main/resources/META-INF/services/org.apache.hadoop.fs.FileSystem diff --git a/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml b/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml index 52b58eddb1e..f742ba824f9 100644 --- a/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml +++ b/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml @@ -849,6 +849,12 @@ + + fs.swift.impl + org.apache.hadoop.fs.swift.snative.SwiftNativeFileSystem + The implementation class of the OpenStack Swift Filesystem + + fs.automatic.close true diff --git a/hadoop-tools/hadoop-openstack/src/main/resources/META-INF/services/org.apache.hadoop.fs.FileSystem b/hadoop-tools/hadoop-openstack/src/main/resources/META-INF/services/org.apache.hadoop.fs.FileSystem deleted file mode 100644 index 649ea311a8e..00000000000 --- a/hadoop-tools/hadoop-openstack/src/main/resources/META-INF/services/org.apache.hadoop.fs.FileSystem +++ /dev/null @@ -1,16 +0,0 @@ -# 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. - -org.apache.hadoop.fs.swift.snative.SwiftNativeFileSystem From 287ba4ffa66212c02e1b1edc8fca53f6368a9efc Mon Sep 17 00:00:00 2001 From: Robert Kanter Date: Wed, 8 Mar 2017 10:45:33 -0800 Subject: [PATCH 034/188] YARN-6297. TestAppLogAggregatorImp.verifyFilesUploaded() should check # of filed uploaded with that of files expected (haibochen via rkanter) --- .../logaggregation/TestAppLogAggregatorImpl.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/logaggregation/TestAppLogAggregatorImpl.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/logaggregation/TestAppLogAggregatorImpl.java index 2602d55603c..17d527a7a5d 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/logaggregation/TestAppLogAggregatorImpl.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/logaggregation/TestAppLogAggregatorImpl.java @@ -146,7 +146,7 @@ public class TestAppLogAggregatorImpl { verifyLogAggregationWithExpectedFiles2DeleteAndUpload(applicationId, containerId, logRententionSec, recoveredLogInitedTimeMillis, - logFiles, new HashSet()); + logFiles, logFiles); } @Test @@ -170,7 +170,7 @@ public class TestAppLogAggregatorImpl { final long week = 7 * 24 * 60 * 60; final long recoveredLogInitedTimeMillis = System.currentTimeMillis() - - 2*week; + 2 * week * 1000; verifyLogAggregationWithExpectedFiles2DeleteAndUpload( applicationId, containerId, week, recoveredLogInitedTimeMillis, logFiles, new HashSet()); @@ -257,7 +257,7 @@ public class TestAppLogAggregatorImpl { Set filesExpected) { final String errMsgPrefix = "The set of files uploaded are not the same " + "as expected"; - if(filesUploaded.size() != filesUploaded.size()) { + if(filesUploaded.size() != filesExpected.size()) { fail(errMsgPrefix + ": actual size: " + filesUploaded.size() + " vs " + "expected size: " + filesExpected.size()); } @@ -413,7 +413,7 @@ public class TestAppLogAggregatorImpl { FileContext lfs, long recoveredLogInitedTime) throws IOException { super(dispatcher, deletionService, conf, appId, ugi, nodeId, dirsHandler, remoteNodeLogFileForApp, appAcls, - logAggregationContext, context, lfs, recoveredLogInitedTime); + logAggregationContext, context, lfs, -1, recoveredLogInitedTime); this.applicationId = appId; this.deletionService = deletionService; From 241c1cc05b71f8b719a85c06e3df930639630726 Mon Sep 17 00:00:00 2001 From: Jian He Date: Wed, 8 Mar 2017 10:48:27 -0800 Subject: [PATCH 035/188] HADOOP-14062. ApplicationMasterProtocolPBClientImpl.allocate fails with EOFException when RPC privacy is enabled. Contributed by Steven Rand --- .../java/org/apache/hadoop/ipc/Client.java | 4 +++- .../yarn/client/api/impl/TestAMRMClient.java | 24 +++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/ipc/Client.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/ipc/Client.java index 70b902c7bb9..c0a5be95f9d 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/ipc/Client.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/ipc/Client.java @@ -1768,7 +1768,9 @@ public class Client implements AutoCloseable { } void setSaslClient(SaslRpcClient client) throws IOException { - setInputStream(client.getInputStream(in)); + // Wrap the input stream in a BufferedInputStream to fill the buffer + // before reading its length (HADOOP-14062). + setInputStream(new BufferedInputStream(client.getInputStream(in))); setOutputStream(client.getOutputStream(out)); } diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-client/src/test/java/org/apache/hadoop/yarn/client/api/impl/TestAMRMClient.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-client/src/test/java/org/apache/hadoop/yarn/client/api/impl/TestAMRMClient.java index 43c02710f8c..a52963a2a87 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-client/src/test/java/org/apache/hadoop/yarn/client/api/impl/TestAMRMClient.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-client/src/test/java/org/apache/hadoop/yarn/client/api/impl/TestAMRMClient.java @@ -137,6 +137,11 @@ public class TestAMRMClient { // set the minimum allocation so that resource decrease can go under 1024 conf.setInt(YarnConfiguration.RM_SCHEDULER_MINIMUM_ALLOCATION_MB, 512); conf.setLong(YarnConfiguration.NM_LOG_RETAIN_SECONDS, 1); + createClientAndCluster(conf); + } + + private static void createClientAndCluster(Configuration conf) + throws Exception { yarnCluster = new MiniYARNCluster(TestAMRMClient.class.getName(), nodeCount, 1, 1); yarnCluster.init(conf); yarnCluster.start(); @@ -861,6 +866,25 @@ public class TestAMRMClient { initAMRMClientAndTest(false); } + @Test (timeout=60000) + public void testAMRMClientWithSaslEncryption() throws Exception { + conf.set("hadoop.rpc.protection", "privacy"); + // we have to create a new instance of MiniYARNCluster to avoid SASL qop + // mismatches between client and server + tearDown(); + createClientAndCluster(conf); + startApp(); + initAMRMClientAndTest(false); + + // recreate the original MiniYARNCluster and YarnClient for other tests + conf.unset("hadoop.rpc.protection"); + tearDown(); + createClientAndCluster(conf); + // unless we start an application the cancelApp() method will fail when + // it runs after this test + startApp(); + } + @Test (timeout=60000) public void testAMRMClientAllocReqId() throws YarnException, IOException { initAMRMClientAndTest(true); From 2be8947d12714c49ef7a90de82a351d086b435b6 Mon Sep 17 00:00:00 2001 From: Jian He Date: Wed, 8 Mar 2017 13:20:01 -0800 Subject: [PATCH 036/188] Revert "HADOOP-14062. ApplicationMasterProtocolPBClientImpl.allocate fails with EOFException when RPC privacy is enabled. Contributed by Steven Rand" This reverts commit 241c1cc05b71f8b719a85c06e3df930639630726. --- .../java/org/apache/hadoop/ipc/Client.java | 4 +--- .../yarn/client/api/impl/TestAMRMClient.java | 24 ------------------- 2 files changed, 1 insertion(+), 27 deletions(-) diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/ipc/Client.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/ipc/Client.java index c0a5be95f9d..70b902c7bb9 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/ipc/Client.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/ipc/Client.java @@ -1768,9 +1768,7 @@ public class Client implements AutoCloseable { } void setSaslClient(SaslRpcClient client) throws IOException { - // Wrap the input stream in a BufferedInputStream to fill the buffer - // before reading its length (HADOOP-14062). - setInputStream(new BufferedInputStream(client.getInputStream(in))); + setInputStream(client.getInputStream(in)); setOutputStream(client.getOutputStream(out)); } diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-client/src/test/java/org/apache/hadoop/yarn/client/api/impl/TestAMRMClient.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-client/src/test/java/org/apache/hadoop/yarn/client/api/impl/TestAMRMClient.java index a52963a2a87..43c02710f8c 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-client/src/test/java/org/apache/hadoop/yarn/client/api/impl/TestAMRMClient.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-client/src/test/java/org/apache/hadoop/yarn/client/api/impl/TestAMRMClient.java @@ -137,11 +137,6 @@ public class TestAMRMClient { // set the minimum allocation so that resource decrease can go under 1024 conf.setInt(YarnConfiguration.RM_SCHEDULER_MINIMUM_ALLOCATION_MB, 512); conf.setLong(YarnConfiguration.NM_LOG_RETAIN_SECONDS, 1); - createClientAndCluster(conf); - } - - private static void createClientAndCluster(Configuration conf) - throws Exception { yarnCluster = new MiniYARNCluster(TestAMRMClient.class.getName(), nodeCount, 1, 1); yarnCluster.init(conf); yarnCluster.start(); @@ -866,25 +861,6 @@ public class TestAMRMClient { initAMRMClientAndTest(false); } - @Test (timeout=60000) - public void testAMRMClientWithSaslEncryption() throws Exception { - conf.set("hadoop.rpc.protection", "privacy"); - // we have to create a new instance of MiniYARNCluster to avoid SASL qop - // mismatches between client and server - tearDown(); - createClientAndCluster(conf); - startApp(); - initAMRMClientAndTest(false); - - // recreate the original MiniYARNCluster and YarnClient for other tests - conf.unset("hadoop.rpc.protection"); - tearDown(); - createClientAndCluster(conf); - // unless we start an application the cancelApp() method will fail when - // it runs after this test - startApp(); - } - @Test (timeout=60000) public void testAMRMClientAllocReqId() throws YarnException, IOException { initAMRMClientAndTest(true); From d7762a55113a529abd6f4ecb8e6d9b0a84b56e08 Mon Sep 17 00:00:00 2001 From: Jason Lowe Date: Wed, 8 Mar 2017 16:46:09 -0600 Subject: [PATCH 037/188] YARN-6165. Intra-queue preemption occurs even when preemption is turned off for a specific queue. Contributed by Eric Payne --- .../IntraQueueCandidatesSelector.java | 5 ++ ...nalCapacityPreemptionPolicyIntraQueue.java | 55 +++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/monitor/capacity/IntraQueueCandidatesSelector.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/monitor/capacity/IntraQueueCandidatesSelector.java index 4f2b272cf7a..289041499d7 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/monitor/capacity/IntraQueueCandidatesSelector.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/monitor/capacity/IntraQueueCandidatesSelector.java @@ -112,6 +112,11 @@ public class IntraQueueCandidatesSelector extends PreemptionCandidatesSelector { continue; } + // Don't preempt if disabled for this queue. + if (leafQueue.getPreemptionDisabled()) { + continue; + } + // 5. Calculate the resource to obtain per partition Map resToObtainByPartition = fifoPreemptionComputePlugin .getResourceDemandFromAppsPerQueue(queueName, partition); diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/monitor/capacity/TestProportionalCapacityPreemptionPolicyIntraQueue.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/monitor/capacity/TestProportionalCapacityPreemptionPolicyIntraQueue.java index 19fb0d293d0..bf83e1c7387 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/monitor/capacity/TestProportionalCapacityPreemptionPolicyIntraQueue.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/monitor/capacity/TestProportionalCapacityPreemptionPolicyIntraQueue.java @@ -105,6 +105,61 @@ public class TestProportionalCapacityPreemptionPolicyIntraQueue getAppAttemptId(3)))); } + @Test + public void testNoIntraQueuePreemptionWithPreemptionDisabledOnQueues() + throws IOException { + /** + * This test has the same configuration as testSimpleIntraQueuePreemption + * except that preemption is disabled specifically for each queue. The + * purpose is to test that disabling preemption on a specific queue will + * avoid intra-queue preemption. + */ + conf.setPreemptionDisabled("root.a", true); + conf.setPreemptionDisabled("root.b", true); + conf.setPreemptionDisabled("root.c", true); + conf.setPreemptionDisabled("root.d", true); + + String labelsConfig = "=100,true;"; + String nodesConfig = // n1 has no label + "n1= res=100"; + String queuesConfig = + // guaranteed,max,used,pending,reserved + "root(=[100 100 80 120 0]);" + // root + "-a(=[11 100 11 50 0]);" + // a + "-b(=[40 100 38 60 0]);" + // b + "-c(=[20 100 10 10 0]);" + // c + "-d(=[29 100 20 0 0])"; // d + + String appsConfig = + // queueName\t(priority,resource,host,expression,#repeat,reserved, + // pending) + "a\t" // app1 in a + + "(1,1,n1,,6,false,25);" + // app1 a + "a\t" // app2 in a + + "(1,1,n1,,5,false,25);" + // app2 a + "b\t" // app3 in b + + "(4,1,n1,,34,false,20);" + // app3 b + "b\t" // app4 in b + + "(4,1,n1,,2,false,10);" + // app4 b + "b\t" // app4 in b + + "(5,1,n1,,1,false,10);" + // app5 b + "b\t" // app4 in b + + "(6,1,n1,,1,false,10);" + // app6 in b + "c\t" // app1 in a + + "(1,1,n1,,10,false,10);" + "d\t" // app7 in c + + "(1,1,n1,,20,false,0)"; + + buildEnv(labelsConfig, nodesConfig, queuesConfig, appsConfig); + policy.editSchedule(); + + verify(mDisp, times(0)).handle(argThat( + new TestProportionalCapacityPreemptionPolicy.IsPreemptionRequestFor( + getAppAttemptId(4)))); + verify(mDisp, times(0)).handle(argThat( + new TestProportionalCapacityPreemptionPolicy.IsPreemptionRequestFor( + getAppAttemptId(3)))); + } + @Test public void testNoPreemptionForSamePriorityApps() throws IOException { /** From 5ca6ef0c268b1acb3abf12505b9ead6fe7e38a23 Mon Sep 17 00:00:00 2001 From: Andrew Wang Date: Wed, 8 Mar 2017 15:36:19 -0800 Subject: [PATCH 038/188] HDFS-10983. OIV tool should make an EC file explicit. Contributed by Manoj Govindassamy. --- .../server/namenode/FSImageFormatPBINode.java | 1 + .../OfflineImageReconstructor.java | 4 + .../offlineImageViewer/PBImageXmlWriter.java | 15 ++- .../hdfs/server/namenode/TestFSImage.java | 1 + .../TestOfflineImageViewer.java | 99 ++++++++++++++++++- 5 files changed, 112 insertions(+), 8 deletions(-) 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 index 17b1da7422f..ef334f7fd4c 100644 --- 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 @@ -332,6 +332,7 @@ public final class FSImageFormatPBINode { BlockType blockType = PBHelperClient.convert(f.getBlockType()); LoaderContext state = parent.getLoaderContext(); boolean isStriped = f.hasErasureCodingPolicyID(); + assert ((!isStriped) || (isStriped && !f.hasReplication())); Short replication = (!isStriped ? (short) f.getReplication() : null); ErasureCodingPolicy ecPolicy = isStriped ? ErasureCodingPolicyManager.getPolicyByPolicyID( diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/tools/offlineImageViewer/OfflineImageReconstructor.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/tools/offlineImageViewer/OfflineImageReconstructor.java index ed348d39033..e80f4d43e68 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/tools/offlineImageViewer/OfflineImageReconstructor.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/tools/offlineImageViewer/OfflineImageReconstructor.java @@ -647,6 +647,10 @@ class OfflineImageReconstructor { break; case "STRIPED": bld.setBlockType(HdfsProtos.BlockTypeProto.STRIPED); + ival = node.removeChildInt(INODE_SECTION_EC_POLICY_ID); + if (ival != null) { + bld.setErasureCodingPolicyID(ival); + } break; default: throw new IOException("INode XML found with unknown " + 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 index f8734cbe293..5a42a6b7b02 100644 --- 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 @@ -40,7 +40,6 @@ import org.apache.hadoop.hdfs.protocol.proto.ClientNamenodeProtocolProtos.CacheD 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.protocol.proto.HdfsProtos.BlockTypeProto; import org.apache.hadoop.hdfs.protocol.proto.XAttrProtos; import org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode; import org.apache.hadoop.hdfs.server.namenode.FSImageFormatProtobuf.SectionName; @@ -59,6 +58,7 @@ 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.hdfs.server.namenode.INodeFile; import org.apache.hadoop.hdfs.util.XMLUtils; import org.apache.hadoop.util.LimitInputStream; import com.google.common.collect.ImmutableList; @@ -132,6 +132,8 @@ public final class PBImageXmlWriter { public static final String INODE_SECTION_STORAGE_POLICY_ID = "storagePolicyId"; public static final String INODE_SECTION_BLOCK_TYPE = "blockType"; + public static final String INODE_SECTION_EC_POLICY_ID = + "erasureCodingPolicyId"; public static final String INODE_SECTION_NS_QUOTA = "nsquota"; public static final String INODE_SECTION_DS_QUOTA = "dsquota"; public static final String INODE_SECTION_TYPE_QUOTA = "typeQuota"; @@ -472,8 +474,12 @@ public final class PBImageXmlWriter { } private void dumpINodeFile(INodeSection.INodeFile f) { - o(SECTION_REPLICATION, f.getReplication()) - .o(INODE_SECTION_MTIME, f.getModificationTime()) + if (f.hasErasureCodingPolicyID()) { + o(SECTION_REPLICATION, INodeFile.DEFAULT_REPL_FOR_STRIPED_BLOCKS); + } else { + o(SECTION_REPLICATION, f.getReplication()); + } + o(INODE_SECTION_MTIME, f.getModificationTime()) .o(INODE_SECTION_ATIME, f.getAccessTime()) .o(INODE_SECTION_PREFERRED_BLOCK_SIZE, f.getPreferredBlockSize()) .o(INODE_SECTION_PERMISSION, dumpPermission(f.getPermission())); @@ -495,8 +501,9 @@ public final class PBImageXmlWriter { if (f.hasStoragePolicyID()) { o(INODE_SECTION_STORAGE_POLICY_ID, f.getStoragePolicyID()); } - if (f.getBlockType() != BlockTypeProto.CONTIGUOUS) { + if (f.hasErasureCodingPolicyID()) { o(INODE_SECTION_BLOCK_TYPE, f.getBlockType().name()); + o(INODE_SECTION_EC_POLICY_ID, f.getErasureCodingPolicyID()); } if (f.hasFileUC()) { 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 index ae154917867..57416ed16d7 100644 --- 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 @@ -223,6 +223,7 @@ public class TestFSImage { // blocks to/from legacy fsimage assertEquals(3, fileByLoaded.getBlocks().length); assertEquals(preferredBlockSize, fileByLoaded.getPreferredBlockSize()); + assertEquals(file.getFileReplication(), fileByLoaded.getFileReplication()); if (isUC) { assertEquals(client, 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 b587017b91b..e52d5365981 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 @@ -76,11 +76,13 @@ import org.apache.hadoop.hdfs.DFSConfigKeys; import org.apache.hadoop.hdfs.DFSTestUtil; import org.apache.hadoop.hdfs.DistributedFileSystem; import org.apache.hadoop.hdfs.MiniDFSCluster; +import org.apache.hadoop.hdfs.protocol.BlockType; import org.apache.hadoop.hdfs.protocol.ErasureCodingPolicy; import org.apache.hadoop.hdfs.protocol.HdfsConstants; import org.apache.hadoop.hdfs.protocol.HdfsConstants.SafeModeAction; import org.apache.hadoop.hdfs.server.namenode.ErasureCodingPolicyManager; import org.apache.hadoop.hdfs.server.namenode.FSImageTestUtil; +import org.apache.hadoop.hdfs.server.namenode.INodeFile; import org.apache.hadoop.hdfs.server.namenode.NameNodeLayoutVersion; import org.apache.hadoop.hdfs.web.WebHdfsFileSystem; import org.apache.hadoop.io.IOUtils; @@ -91,9 +93,8 @@ import org.apache.log4j.Level; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.TemporaryFolder; +import org.xml.sax.Attributes; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; @@ -112,7 +113,6 @@ public class TestOfflineImageViewer { // namespace as written to dfs, to be compared with viewer's output final static HashMap writtenFiles = Maps.newHashMap(); static int dirCount = 0; - private static File tempDir; // Create a populated namespace for later testing. Save its contents to a @@ -358,6 +358,96 @@ public class TestOfflineImageViewer { assertEquals(0, status); } + /** + * SAX handler to verify EC Files and their policies. + */ + class ECXMLHandler extends DefaultHandler { + + private boolean isInode = false; + private boolean isAttrRepl = false; + private boolean isAttrName = false; + private boolean isXAttrs = false; + private boolean isAttrECPolicy = false; + private boolean isAttrBlockType = false; + private String currentInodeName; + private String currentECPolicy; + private String currentBlockType; + private String currentRepl; + + @Override + public void startElement(String uri, String localName, String qName, + Attributes attributes) throws SAXException { + super.startElement(uri, localName, qName, attributes); + if (qName.equalsIgnoreCase(PBImageXmlWriter.INODE_SECTION_INODE)) { + isInode = true; + } else if (isInode && !isXAttrs && qName.equalsIgnoreCase( + PBImageXmlWriter.SECTION_NAME)) { + isAttrName = true; + } else if (isInode && qName.equalsIgnoreCase( + PBImageXmlWriter.SECTION_REPLICATION)) { + isAttrRepl = true; + } else if (isInode && + qName.equalsIgnoreCase(PBImageXmlWriter.INODE_SECTION_EC_POLICY_ID)) { + isAttrECPolicy = true; + } else if (isInode && qName.equalsIgnoreCase( + PBImageXmlWriter.INODE_SECTION_BLOCK_TYPE)) { + isAttrBlockType = true; + } else if (isInode && qName.equalsIgnoreCase( + PBImageXmlWriter.INODE_SECTION_XATTRS)) { + isXAttrs = true; + } + } + + @Override + public void endElement(String uri, String localName, String qName) + throws SAXException { + super.endElement(uri, localName, qName); + if (qName.equalsIgnoreCase(PBImageXmlWriter.INODE_SECTION_INODE)) { + if (currentInodeName != null && currentInodeName.length() > 0) { + if (currentBlockType != null && currentBlockType.equalsIgnoreCase( + BlockType.STRIPED.name())) { + Assert.assertEquals("INode '" + + currentInodeName + "' has unexpected EC Policy!", + Byte.parseByte(currentECPolicy), + ErasureCodingPolicyManager.getPolicyByPolicyID( + HdfsConstants.XOR_2_1_POLICY_ID).getId()); + Assert.assertEquals("INode '" + + currentInodeName + "' has unexpected replication!", + currentRepl, + Short.toString(INodeFile.DEFAULT_REPL_FOR_STRIPED_BLOCKS)); + } + } + isInode = false; + currentInodeName = ""; + currentECPolicy = ""; + currentRepl = ""; + } else if (qName.equalsIgnoreCase( + PBImageXmlWriter.INODE_SECTION_XATTRS)) { + isXAttrs = false; + } + } + + @Override + public void characters(char[] ch, int start, int length) + throws SAXException { + super.characters(ch, start, length); + String value = new String(ch, start, length); + if (isAttrName) { + currentInodeName = value; + isAttrName = false; + } else if (isAttrRepl) { + currentRepl = value; + isAttrRepl = false; + } else if (isAttrECPolicy) { + currentECPolicy = value; + isAttrECPolicy = false; + } else if (isAttrBlockType) { + currentBlockType = value; + isAttrBlockType = false; + } + } + } + @Test public void testPBImageXmlWriter() throws IOException, SAXException, ParserConfigurationException { @@ -368,7 +458,8 @@ public class TestOfflineImageViewer { SAXParserFactory spf = SAXParserFactory.newInstance(); SAXParser parser = spf.newSAXParser(); final String xml = output.toString(); - parser.parse(new InputSource(new StringReader(xml)), new DefaultHandler()); + ECXMLHandler ecxmlHandler = new ECXMLHandler(); + parser.parse(new InputSource(new StringReader(xml)), ecxmlHandler); } @Test From 33a38a534110de454662256545a7f4c075d328c8 Mon Sep 17 00:00:00 2001 From: Andrew Wang Date: Wed, 8 Mar 2017 16:41:44 -0800 Subject: [PATCH 039/188] HDFS-11314. Enforce set of enabled EC policies on the NameNode. --- .../org/apache/hadoop/hdfs/DFSConfigKeys.java | 2 + .../namenode/ErasureCodingPolicyManager.java | 101 ++++++++---- .../server/namenode/FSDirErasureCodingOp.java | 54 +++---- .../server/namenode/FSImageFormatPBINode.java | 2 +- .../hdfs/server/namenode/FSNamesystem.java | 2 +- .../hdfs/server/namenode/INodeFile.java | 4 +- .../org/apache/hadoop/hdfs/tools/ECAdmin.java | 24 ++- .../src/main/resources/hdfs-default.xml | 9 ++ .../src/site/markdown/HDFSErasureCoding.md | 45 ++++-- .../hadoop/cli/TestErasureCodingCLI.java | 3 + .../org/apache/hadoop/hdfs/DFSTestUtil.java | 11 ++ ...estDFSRSDefault10x4StripedInputStream.java | 2 +- ...stDFSRSDefault10x4StripedOutputStream.java | 2 +- ...ult10x4StripedOutputStreamWithFailure.java | 2 +- .../hdfs/TestDFSStripedInputStream.java | 2 + .../hdfs/TestDFSStripedOutputStream.java | 1 + ...TestDFSStripedOutputStreamWithFailure.java | 1 + .../hdfs/TestDFSXORStripedInputStream.java | 2 +- .../hdfs/TestDFSXORStripedOutputStream.java | 2 +- ...tDFSXORStripedOutputStreamWithFailure.java | 2 +- .../hdfs/TestErasureCodingPolicies.java | 41 ++++- .../TestUnsetAndChangeDirectoryEcPolicy.java | 5 +- .../namenode/TestEnabledECPolicies.java | 151 ++++++++++++++++++ .../hdfs/server/namenode/TestFSImage.java | 9 +- .../TestOfflineImageViewer.java | 11 +- .../test/resources/testErasureCodingConf.xml | 21 ++- 26 files changed, 401 insertions(+), 110 deletions(-) create mode 100644 hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestEnabledECPolicies.java diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/DFSConfigKeys.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/DFSConfigKeys.java index 82d6073399a..3fc4980648d 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/DFSConfigKeys.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/DFSConfigKeys.java @@ -562,6 +562,8 @@ public class DFSConfigKeys extends CommonConfigurationKeys { public static final String DFS_DATANODE_DISK_CHECK_TIMEOUT_DEFAULT = "10m"; + public static final String DFS_NAMENODE_EC_POLICIES_ENABLED_KEY = "dfs.namenode.ec.policies.enabled"; + public static final String DFS_NAMENODE_EC_POLICIES_ENABLED_DEFAULT = "RS-6-3-64k"; public static final String DFS_DN_EC_RECONSTRUCTION_STRIPED_READ_THREADS_KEY = "dfs.datanode.ec.reconstruction.stripedread.threads"; public static final int DFS_DN_EC_RECONSTRUCTION_STRIPED_READ_THREADS_DEFAULT = 20; public static final String DFS_DN_EC_RECONSTRUCTION_STRIPED_READ_BUFFER_SIZE_KEY = "dfs.datanode.ec.reconstruction.stripedread.buffer.size"; diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/ErasureCodingPolicyManager.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/ErasureCodingPolicyManager.java index a1b22708b20..02cbbdf38fd 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/ErasureCodingPolicyManager.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/ErasureCodingPolicyManager.java @@ -18,12 +18,17 @@ package org.apache.hadoop.hdfs.server.namenode; import org.apache.hadoop.classification.InterfaceAudience; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hdfs.DFSConfigKeys; import org.apache.hadoop.hdfs.protocol.ErasureCodingPolicy; import org.apache.hadoop.hdfs.protocol.HdfsConstants; import org.apache.hadoop.io.erasurecode.ErasureCodeConstants; +import java.util.Arrays; import java.util.Map; import java.util.TreeMap; +import java.util.stream.Collectors; + /** * This manages erasure coding policies predefined and activated in the system. @@ -61,21 +66,52 @@ public final class ErasureCodingPolicyManager { SYS_POLICY4, SYS_POLICY5}; // Supported storage policies for striped EC files - private static final byte[] SUITABLE_STORAGE_POLICIES_FOR_EC_STRIPED_MODE = new byte[] { - HdfsConstants.HOT_STORAGE_POLICY_ID, HdfsConstants.COLD_STORAGE_POLICY_ID, - HdfsConstants.ALLSSD_STORAGE_POLICY_ID }; - + private static final byte[] SUITABLE_STORAGE_POLICIES_FOR_EC_STRIPED_MODE = + new byte[]{ + HdfsConstants.HOT_STORAGE_POLICY_ID, + HdfsConstants.COLD_STORAGE_POLICY_ID, + HdfsConstants.ALLSSD_STORAGE_POLICY_ID}; /** - * All active policies maintained in NN memory for fast querying, + * All supported policies maintained in NN memory for fast querying, * identified and sorted by its name. */ - private final Map activePoliciesByName; + private static final Map SYSTEM_POLICIES_BY_NAME; - ErasureCodingPolicyManager() { - - this.activePoliciesByName = new TreeMap<>(); + static { + // Create a hashmap of all available policies for quick lookup by name + SYSTEM_POLICIES_BY_NAME = new TreeMap<>(); for (ErasureCodingPolicy policy : SYS_POLICIES) { - activePoliciesByName.put(policy.getName(), policy); + SYSTEM_POLICIES_BY_NAME.put(policy.getName(), policy); + } + } + + /** + * All enabled policies maintained in NN memory for fast querying, + * identified and sorted by its name. + */ + private final Map enabledPoliciesByName; + + ErasureCodingPolicyManager(Configuration conf) { + // Populate the list of enabled policies from configuration + final String[] policyNames = conf.getTrimmedStrings( + DFSConfigKeys.DFS_NAMENODE_EC_POLICIES_ENABLED_KEY, + DFSConfigKeys.DFS_NAMENODE_EC_POLICIES_ENABLED_DEFAULT); + this.enabledPoliciesByName = new TreeMap<>(); + for (String policyName : policyNames) { + ErasureCodingPolicy ecPolicy = SYSTEM_POLICIES_BY_NAME.get(policyName); + if (ecPolicy == null) { + String sysPolicies = Arrays.asList(SYS_POLICIES).stream() + .map(ErasureCodingPolicy::getName) + .collect(Collectors.joining(", ")); + String msg = String.format("EC policy %s specified at %s is not a " + + "valid policy. Please choose from list of available policies: " + + "[%s]", + policyName, + DFSConfigKeys.DFS_NAMENODE_EC_POLICIES_ENABLED_KEY, + sysPolicies); + throw new IllegalArgumentException(msg); + } + enabledPoliciesByName.put(ecPolicy.getName(), ecPolicy); } /** @@ -104,10 +140,10 @@ public final class ErasureCodingPolicyManager { } /** - * Get system-wide policy by policy ID. - * @return ecPolicy + * Get a policy by policy ID. + * @return ecPolicy, or null if not found */ - public static ErasureCodingPolicy getPolicyByPolicyID(byte id) { + public static ErasureCodingPolicy getPolicyByID(byte id) { for (ErasureCodingPolicy policy : SYS_POLICIES) { if (policy.getId() == id) { return policy; @@ -117,36 +153,33 @@ public final class ErasureCodingPolicyManager { } /** - * Get all policies that's available to use. + * Get a policy by policy name. + * @return ecPolicy, or null if not found + */ + public static ErasureCodingPolicy getPolicyByName(String name) { + return SYSTEM_POLICIES_BY_NAME.get(name); + } + + /** + * Get the set of enabled policies. * @return all policies */ - public ErasureCodingPolicy[] getPolicies() { + public ErasureCodingPolicy[] getEnabledPolicies() { ErasureCodingPolicy[] results = - new ErasureCodingPolicy[activePoliciesByName.size()]; - return activePoliciesByName.values().toArray(results); + new ErasureCodingPolicy[enabledPoliciesByName.size()]; + return enabledPoliciesByName.values().toArray(results); } /** - * Get the policy specified by the policy name. + * Get enabled policy by policy name. */ - public ErasureCodingPolicy getPolicyByName(String name) { - return activePoliciesByName.get(name); + public ErasureCodingPolicy getEnabledPolicyByName(String name) { + return enabledPoliciesByName.get(name); } /** - * Get the policy specified by the policy ID. - */ - public ErasureCodingPolicy getPolicyByID(byte id) { - for (ErasureCodingPolicy policy : activePoliciesByName.values()) { - if (policy.getId() == id) { - return policy; - } - } - return null; - } - - /** - * @return True if given policy is be suitable for striped EC Files. + * @return if the specified storage policy ID is suitable for striped EC + * files. */ public static boolean checkStoragePolicySuitableForECStripedMode( byte storagePolicyID) { @@ -164,6 +197,6 @@ public final class ErasureCodingPolicyManager { * Clear and clean up. */ public void clear() { - activePoliciesByName.clear(); + enabledPoliciesByName.clear(); } } diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSDirErasureCodingOp.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSDirErasureCodingOp.java index 30a7b8bbe64..50843c87066 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSDirErasureCodingOp.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSDirErasureCodingOp.java @@ -23,9 +23,10 @@ import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.FileNotFoundException; import java.io.IOException; -import java.util.ArrayList; +import java.util.Arrays; import java.util.EnumSet; import java.util.List; +import java.util.stream.Collectors; import com.google.common.base.Preconditions; import com.google.common.collect.Lists; @@ -34,6 +35,7 @@ import org.apache.hadoop.HadoopIllegalArgumentException; import org.apache.hadoop.fs.XAttr; import org.apache.hadoop.fs.XAttrSetFlag; import org.apache.hadoop.fs.permission.FsAction; +import org.apache.hadoop.hdfs.DFSConfigKeys; import org.apache.hadoop.hdfs.XAttrHelper; import org.apache.hadoop.hdfs.protocol.ErasureCodingPolicy; import org.apache.hadoop.hdfs.protocol.HdfsFileStatus; @@ -82,11 +84,23 @@ final class FSDirErasureCodingOp { fsd.writeLock(); try { ErasureCodingPolicy ecPolicy = fsn.getErasureCodingPolicyManager() - .getPolicyByName(ecPolicyName); + .getEnabledPolicyByName(ecPolicyName); if (ecPolicy == null) { - throw new HadoopIllegalArgumentException("Policy '" + - ecPolicyName + "' does not match any supported erasure coding " + - "policies."); + final String sysPolicies = + Arrays.asList( + fsn.getErasureCodingPolicyManager().getEnabledPolicies()) + .stream() + .map(ErasureCodingPolicy::getName) + .collect(Collectors.joining(", ")); + final String message = String.format("Policy '%s' does not match any " + + "enabled erasure" + + " coding policies: [%s]. The set of enabled erasure coding " + + "policies can be configured at '%s'.", + ecPolicyName, + sysPolicies, + DFSConfigKeys.DFS_NAMENODE_EC_POLICIES_ENABLED_KEY + ); + throw new HadoopIllegalArgumentException(message); } iip = fsd.resolvePath(pc, src, DirOp.WRITE_LINK); // Write access is required to set erasure coding policy @@ -118,26 +132,6 @@ final class FSDirErasureCodingOp { "for a file " + src); } - // Check that the EC policy is one of the active policies. - boolean validPolicy = false; - ErasureCodingPolicy[] activePolicies = - FSDirErasureCodingOp.getErasureCodingPolicies(fsd.getFSNamesystem()); - for (ErasureCodingPolicy activePolicy : activePolicies) { - if (activePolicy.equals(ecPolicy)) { - validPolicy = true; - break; - } - } - if (!validPolicy) { - List ecPolicyNames = new ArrayList(); - for (ErasureCodingPolicy activePolicy : activePolicies) { - ecPolicyNames.add(activePolicy.getName()); - } - throw new HadoopIllegalArgumentException("Policy [ " + - ecPolicy.getName() + " ] does not match any of the " + - "supported policies. Please select any one of " + ecPolicyNames); - } - final XAttr ecXAttr; DataOutputStream dOut = null; try { @@ -291,7 +285,7 @@ final class FSDirErasureCodingOp { static ErasureCodingPolicy[] getErasureCodingPolicies(final FSNamesystem fsn) throws IOException { assert fsn.hasReadLock(); - return fsn.getErasureCodingPolicyManager().getPolicies(); + return fsn.getErasureCodingPolicyManager().getEnabledPolicies(); } private static ErasureCodingPolicy getErasureCodingPolicyForPath( @@ -307,8 +301,8 @@ final class FSDirErasureCodingOp { } if (inode.isFile()) { byte id = inode.asFile().getErasureCodingPolicyID(); - return id < 0 ? null : fsd.getFSNamesystem(). - getErasureCodingPolicyManager().getPolicyByID(id); + return id < 0 ? null : + ErasureCodingPolicyManager.getPolicyByID(id); } // We don't allow setting EC policies on paths with a symlink. Thus // if a symlink is encountered, the dir shouldn't have EC policy. @@ -323,8 +317,8 @@ final class FSDirErasureCodingOp { ByteArrayInputStream bIn = new ByteArrayInputStream(xattr.getValue()); DataInputStream dIn = new DataInputStream(bIn); String ecPolicyName = WritableUtils.readString(dIn); - return fsd.getFSNamesystem().getErasureCodingPolicyManager(). - getPolicyByName(ecPolicyName); + return ErasureCodingPolicyManager + .getPolicyByName(ecPolicyName); } } } 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 index ef334f7fd4c..eab728283b3 100644 --- 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 @@ -335,7 +335,7 @@ public final class FSImageFormatPBINode { assert ((!isStriped) || (isStriped && !f.hasReplication())); Short replication = (!isStriped ? (short) f.getReplication() : null); ErasureCodingPolicy ecPolicy = isStriped ? - ErasureCodingPolicyManager.getPolicyByPolicyID( + ErasureCodingPolicyManager.getPolicyByID( (byte) f.getErasureCodingPolicyID()) : null; Byte ecPolicyID = (isStriped ? ecPolicy.getId() : null); 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 03277af446a..062896ce4f7 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 @@ -838,7 +838,7 @@ public class FSNamesystem implements Namesystem, FSNamesystemMBean, this.dir = new FSDirectory(this, conf); this.snapshotManager = new SnapshotManager(dir); this.cacheManager = new CacheManager(this, conf, blockManager); - this.ecPolicyManager = new ErasureCodingPolicyManager(); + this.ecPolicyManager = new ErasureCodingPolicyManager(conf); this.topConf = new TopConf(conf); this.auditLoggers = initAuditLoggers(conf); this.isDefaultAuditLogger = auditLoggers.size() == 1 && 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 19480e45031..2b0e0adea2a 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 @@ -192,7 +192,7 @@ public class INodeFile extends INodeWithAdditionalFields Preconditions.checkArgument(replication == null && erasureCodingPolicyID != null); Preconditions.checkArgument(ErasureCodingPolicyManager - .getPolicyByPolicyID(erasureCodingPolicyID) != null, + .getPolicyByID(erasureCodingPolicyID) != null, "Could not find EC policy with ID 0x" + StringUtils .byteToHexString(erasureCodingPolicyID)); layoutRedundancy |= BLOCK_TYPE_MASK_STRIPED; @@ -514,7 +514,7 @@ public class INodeFile extends INodeWithAdditionalFields } ErasureCodingPolicy ecPolicy = - ErasureCodingPolicyManager.getPolicyByPolicyID( + ErasureCodingPolicyManager.getPolicyByID( getErasureCodingPolicyID()); Preconditions.checkNotNull(ecPolicy, "Could not find EC policy with ID 0x" + StringUtils.byteToHexString(getErasureCodingPolicyID())); diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/tools/ECAdmin.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/tools/ECAdmin.java index 548f754a1eb..52f75342173 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/tools/ECAdmin.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/tools/ECAdmin.java @@ -20,6 +20,7 @@ import org.apache.hadoop.classification.InterfaceAudience; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.conf.Configured; import org.apache.hadoop.fs.Path; +import org.apache.hadoop.hdfs.DFSConfigKeys; import org.apache.hadoop.hdfs.DistributedFileSystem; import org.apache.hadoop.hdfs.protocol.ErasureCodingPolicy; import org.apache.hadoop.tools.TableListing; @@ -79,7 +80,7 @@ public class ECAdmin extends Configured implements Tool { } } - /** Command to list the set of available erasure coding policies */ + /** Command to list the set of enabled erasure coding policies. */ private static class ListECPoliciesCommand implements AdminHelper.Command { @Override @@ -95,7 +96,9 @@ public class ECAdmin extends Configured implements Tool { @Override public String getLongUsage() { return getShortUsage() + "\n" + - "Get the list of supported erasure coding policies.\n"; + "Get the list of enabled erasure coding policies.\n" + + "Policies can be enabled on the NameNode via `" + + DFSConfigKeys.DFS_NAMENODE_EC_POLICIES_ENABLED_KEY + "`.\n"; } @Override @@ -109,10 +112,19 @@ public class ECAdmin extends Configured implements Tool { try { Collection policies = dfs.getAllErasureCodingPolicies(); - System.out.println("Erasure Coding Policies:"); - for (ErasureCodingPolicy policy : policies) { - if (policy != null) { - System.out.println("\t" + policy.getName()); + if (policies.isEmpty()) { + System.out.println("No erasure coding policies are enabled on the " + + "cluster."); + System.out.println("The set of enabled policies can be " + + "configured at '" + + DFSConfigKeys.DFS_NAMENODE_EC_POLICIES_ENABLED_KEY + "' on the " + + "NameNode."); + } else { + System.out.println("Erasure Coding Policies:"); + for (ErasureCodingPolicy policy : policies) { + if (policy != null) { + System.out.println("\t" + policy.getName()); + } } } } catch (IOException e) { diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/resources/hdfs-default.xml b/hadoop-hdfs-project/hadoop-hdfs/src/main/resources/hdfs-default.xml index 36b93b7e949..fa5d814eaf8 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/resources/hdfs-default.xml +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/resources/hdfs-default.xml @@ -2928,6 +2928,15 @@ + + dfs.namenode.ec.policies.enabled + RS-6-3-64k + Comma-delimited list of enabled erasure coding policies. + The NameNode will enforce this when setting an erasure coding policy + on a directory. + + + dfs.datanode.ec.reconstruction.stripedread.timeout.millis 5000 diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/site/markdown/HDFSErasureCoding.md b/hadoop-hdfs-project/hadoop-hdfs/src/site/markdown/HDFSErasureCoding.md index 36fb61dfabf..9d9f2ce3c6e 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/site/markdown/HDFSErasureCoding.md +++ b/hadoop-hdfs-project/hadoop-hdfs/src/site/markdown/HDFSErasureCoding.md @@ -56,19 +56,26 @@ Architecture 3. _Transfer the generated data blocks to target nodes:_ Once decoding is finished, the recovered blocks are transferred to target DataNodes. - * **ErasureCoding policy** - To accommodate heterogeneous workloads, we allow files and directories in an HDFS cluster to have different replication and EC policies. - Information on how to encode/decode a file is encapsulated in an ErasureCodingPolicy class. Each policy is defined by the following 2 pieces of information: + * **Erasure coding policies** + To accommodate heterogeneous workloads, we allow files and directories in an HDFS cluster to have different replication and erasure coding policies. + The erasure coding policy encapsulates how to encode/decode a file. Each policy is defined by the following pieces of information: - 1. _The ECSchema:_ This includes the numbers of data and parity blocks in an EC group (e.g., 6+3), as well as the codec algorithm (e.g., Reed-Solomon). + 1. _The EC schema:_ This includes the numbers of data and parity blocks in an EC group (e.g., 6+3), as well as the codec algorithm (e.g., Reed-Solomon, XOR). 2. _The size of a striping cell._ This determines the granularity of striped reads and writes, including buffer sizes and encoding work. - Five policies are currently supported: RS-3-2-64k, RS-6-3-64k, RS-10-4-64k, RS-LEGACY-6-3-64k, and XOR-2-1-64k. All with default cell size of 64KB. The system default policy is RS-6-3-64k which use the default schema RS_6_3_SCHEMA with a cell size of 64KB. + Policies are named *codec*-*num data blocks*-*num parity blocks*-*cell size*. Currently, five built-in policies are supported: `RS-3-2-64k`, `RS-6-3-64k`, `RS-10-4-64k`, `RS-LEGACY-6-3-64k`, and `XOR-2-1-64k`. + + By default, only `RS-6-3-64k` is enabled. + + Similar to HDFS storage policies, erasure coding policies are set on a directory. When a file is created, it inherits the EC policy of its nearest ancestor directory. + + Directory-level EC policies only affect new files created within the directory. Once a file has been created, its erasure coding policy can be queried but not changed. If an erasure coded file is renamed to a directory with a different EC policy, the file retains its existing EC policy. Converting a file to a different EC policy requires rewriting its data; do this by copying the file (e.g. via distcp) rather than renaming it. * **Intel ISA-L** - Intel ISA-L stands for Intel Intelligent Storage Acceleration Library. ISA-L is a collection of optimized low-level functions used primarily in storage applications. It includes a fast block Reed-Solomon type erasure codes optimized for Intel AVX and AVX2 instruction sets. - HDFS EC can leverage this open-source library to accelerate encoding and decoding calculation. ISA-L supports most of major operating systems, including Linux and Windows. By default, ISA-L is not enabled in HDFS. + Intel ISA-L stands for Intel Intelligent Storage Acceleration Library. ISA-L is an open-source collection of optimized low-level functions designed for storage applications. It includes fast block Reed-Solomon type erasure codes optimized for Intel AVX and AVX2 instruction sets. + HDFS erasure coding can leverage ISA-L to accelerate encoding and decoding calculation. ISA-L supports most major operating systems, including Linux and Windows. + ISA-L is not enabled by default. See the instructions below for how to enable ISA-L. Deployment ---------- @@ -90,6 +97,10 @@ Deployment ### Configuration keys + The set of enabled erasure coding policies can be configured on the NameNode via `dfs.namenode.ec.policies.enabled`. This restricts what EC policies can be set by clients. It does not affect the behavior of already set file or directory-level EC policies. + + By default, only the `RS-6-3-64k` policy is enabled. Typically, the cluster administrator will configure the set of enabled policies based on the size of the cluster and the desired fault-tolerance properties. For instance, for a cluster with 9 racks, a policy like `RS-10-4-64k` will not preserve rack-level fault-tolerance, and `RS-6-3-64k` or `RS-3-2-64k` might be more appropriate. If the administrator only cares about node-level fault-tolerance, `RS-10-4-64k` would still be appropriate as long as there are at least 14 DataNodes in the cluster. + The codec implementation for Reed-Solomon and XOR can be configured with the following client and DataNode configuration keys: `io.erasurecode.codec.rs.rawcoder` for the default RS codec, `io.erasurecode.codec.rs-legacy.rawcoder` for the legacy RS codec, @@ -106,13 +117,13 @@ Deployment ### Enable Intel ISA-L HDFS native implementation of default RS codec leverages Intel ISA-L library to improve the encoding and decoding calculation. To enable and use Intel ISA-L, there are three steps. - 1. Build ISA-L library. Please refer to the offical site "https://github.com/01org/isa-l/" for detail information. - 2. Build Hadoop with ISA-L support. Please refer to "Intel ISA-L build options" section in "Build instructions for Hadoop"(BUILDING.txt) document. Use -Dbundle.isal to copy the contents of the isal.lib directory into the final tar file. Deploy hadoop with the tar file. Make sure ISA-L library is available on both HDFS client and DataNodes. - 3. Configure the `io.erasurecode.codec.rs.rawcoder` key with value `org.apache.hadoop.io.erasurecode.rawcoder.NativeRSRawErasureCoderFactory` on HDFS client and DataNodes. + 1. Build ISA-L library. Please refer to the official site "https://github.com/01org/isa-l/" for detail information. + 2. Build Hadoop with ISA-L support. Please refer to "Intel ISA-L build options" section in "Build instructions for Hadoop" in (BUILDING.txt) in the source code. Use `-Dbundle.isal` to copy the contents of the `isal.lib` directory into the final tar file. Deploy Hadoop with the tar file. Make sure ISA-L is available on HDFS clients and DataNodes. + 3. Configure the `io.erasurecode.codec.rs.rawcoder` key with value `org.apache.hadoop.io.erasurecode.rawcoder.NativeRSRawErasureCoderFactory` on HDFS clients and DataNodes. - To check ISA-L library enable state, try "Hadoop checknative" command. It will tell you if ISA-L library is enabled or not. + To verify that ISA-L is correctly detected by Hadoop, run the `hadoop checknative` command. - It also requires three steps to enable the native implementation of XOR codec. The first two steps are the same as the above step 1 and step 2. In step 3, configure the `io.erasurecode.codec.xor.rawcoder` key with `org.apache.hadoop.io.erasurecode.rawcoder.NativeXORRawErasureCoderFactory` on both HDFS client and DataNodes. + To enable the native implementation of the XOR codec, perform the same first two steps as above to build and deploy Hadoop with ISA-L support. Afterwards, configure the `io.erasurecode.codec.xor.rawcoder` key with `org.apache.hadoop.io.erasurecode.rawcoder.NativeXORRawErasureCoderFactory` on both HDFS client and DataNodes. ### Administrative commands @@ -130,20 +141,20 @@ Below are the details about each command. * `[-setPolicy -policy -path ]` - Sets an ErasureCoding policy on a directory at the specified path. + Sets an erasure coding policy on a directory at the specified path. `path`: An directory in HDFS. This is a mandatory parameter. Setting a policy only affects newly created files, and does not affect existing files. - `policyName`: The ErasureCoding policy to be used for files under this directory. + `policyName`: The erasure coding policy to be used for files under this directory. * `[-getPolicy -path ]` - Get details of the ErasureCoding policy of a file or directory at the specified path. + Get details of the erasure coding policy of a file or directory at the specified path. * `[-unsetPolicy -path ]` - Unset an ErasureCoding policy set by a previous call to "setPolicy" on a directory. If the directory inherits the ErasureCoding policy from an ancestor directory, "unsetPolicy" is a no-op. Unsetting the policy on a directory which doesn't have an explicit policy set will not return an error. + Unset an erasure coding policy set by a previous call to `setPolicy` on a directory. If the directory inherits the erasure coding policy from an ancestor directory, `unsetPolicy` is a no-op. Unsetting the policy on a directory which doesn't have an explicit policy set will not return an error. * `[-listPolicies]` - Lists all supported ErasureCoding policies. These names are suitable for use with the `setPolicy` command. + Lists the set of enabled erasure coding policies. These names are suitable for use with the `setPolicy` command. diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/cli/TestErasureCodingCLI.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/cli/TestErasureCodingCLI.java index 2f4680322a7..718196871e7 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/cli/TestErasureCodingCLI.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/cli/TestErasureCodingCLI.java @@ -45,6 +45,9 @@ public class TestErasureCodingCLI extends CLITestHelper { public void setUp() throws Exception { super.setUp(); + conf.set(DFSConfigKeys.DFS_NAMENODE_EC_POLICIES_ENABLED_KEY, + "RS-6-3-64k,RS-3-2-64k,XOR-2-1-64k"); + dfsCluster = new MiniDFSCluster.Builder(conf) .numDataNodes(NUM_OF_DATANODES).build(); dfsCluster.waitClusterUp(); diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/DFSTestUtil.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/DFSTestUtil.java index 60a1a4e4373..7bf5cdcf6ec 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/DFSTestUtil.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/DFSTestUtil.java @@ -68,6 +68,8 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; + import com.google.common.base.Charsets; import com.google.common.base.Joiner; import com.google.common.base.Preconditions; @@ -279,6 +281,15 @@ public class DFSTestUtil { Whitebox.setInternalState(fsn.getFSDirectory(), "editLog", newLog); } + public static void enableAllECPolicies(Configuration conf) { + // Enable all the available EC policies + String policies = + Arrays.asList(ErasureCodingPolicyManager.getSystemPolicies()).stream() + .map(ErasureCodingPolicy::getName) + .collect(Collectors.joining(",")); + conf.set(DFSConfigKeys.DFS_NAMENODE_EC_POLICIES_ENABLED_KEY, policies); + } + /** class MyFile contains enough information to recreate the contents of * a single file. */ diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSRSDefault10x4StripedInputStream.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSRSDefault10x4StripedInputStream.java index 3e6d1e40521..fa3e62cbfcc 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSRSDefault10x4StripedInputStream.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSRSDefault10x4StripedInputStream.java @@ -29,7 +29,7 @@ public class TestDFSRSDefault10x4StripedInputStream extends TestDFSStripedInputStream { public ErasureCodingPolicy getEcPolicy() { - return ErasureCodingPolicyManager.getPolicyByPolicyID( + return ErasureCodingPolicyManager.getPolicyByID( HdfsConstants.RS_10_4_POLICY_ID); } } diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSRSDefault10x4StripedOutputStream.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSRSDefault10x4StripedOutputStream.java index 1ea839a863b..f4dcdf735da 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSRSDefault10x4StripedOutputStream.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSRSDefault10x4StripedOutputStream.java @@ -30,7 +30,7 @@ public class TestDFSRSDefault10x4StripedOutputStream @Override public ErasureCodingPolicy getEcPolicy() { - return ErasureCodingPolicyManager.getPolicyByPolicyID( + return ErasureCodingPolicyManager.getPolicyByID( HdfsConstants.RS_10_4_POLICY_ID); } } diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSRSDefault10x4StripedOutputStreamWithFailure.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSRSDefault10x4StripedOutputStreamWithFailure.java index 340fec52fcf..27d3ccfcc3f 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSRSDefault10x4StripedOutputStreamWithFailure.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSRSDefault10x4StripedOutputStreamWithFailure.java @@ -30,7 +30,7 @@ public class TestDFSRSDefault10x4StripedOutputStreamWithFailure @Override public ErasureCodingPolicy getEcPolicy() { - return ErasureCodingPolicyManager.getPolicyByPolicyID( + return ErasureCodingPolicyManager.getPolicyByID( HdfsConstants.RS_10_4_POLICY_ID); } } diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSStripedInputStream.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSStripedInputStream.java index 50b366b26a7..29ef6943af3 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSStripedInputStream.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSStripedInputStream.java @@ -94,6 +94,8 @@ public class TestDFSStripedInputStream { conf.setLong(DFSConfigKeys.DFS_BLOCK_SIZE_KEY, blockSize); conf.setInt(DFSConfigKeys.DFS_NAMENODE_REPLICATION_MAX_STREAMS_KEY, 0); + conf.set(DFSConfigKeys.DFS_NAMENODE_EC_POLICIES_ENABLED_KEY, + getEcPolicy().getName()); if (ErasureCodeNative.isNativeCodeLoaded()) { conf.set( CodecUtil.IO_ERASURECODE_CODEC_RS_RAWCODER_KEY, diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSStripedOutputStream.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSStripedOutputStream.java index 5956cc12390..675f0422c14 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSStripedOutputStream.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSStripedOutputStream.java @@ -88,6 +88,7 @@ public class TestDFSStripedOutputStream { CodecUtil.IO_ERASURECODE_CODEC_RS_RAWCODER_KEY, NativeRSRawErasureCoderFactory.class.getCanonicalName()); } + DFSTestUtil.enableAllECPolicies(conf); cluster = new MiniDFSCluster.Builder(conf).numDataNodes(numDNs).build(); cluster.getFileSystem().getClient().setErasureCodingPolicy("/", ecPolicy .getName()); diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSStripedOutputStreamWithFailure.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSStripedOutputStreamWithFailure.java index 01bd3c569ec..9bd29231fd6 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSStripedOutputStreamWithFailure.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSStripedOutputStreamWithFailure.java @@ -217,6 +217,7 @@ public class TestDFSStripedOutputStreamWithFailure { CodecUtil.IO_ERASURECODE_CODEC_RS_RAWCODER_KEY, NativeRSRawErasureCoderFactory.class.getCanonicalName()); } + DFSTestUtil.enableAllECPolicies(conf); cluster = new MiniDFSCluster.Builder(conf).numDataNodes(numDNs).build(); cluster.waitActive(); dfs = cluster.getFileSystem(); diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSXORStripedInputStream.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSXORStripedInputStream.java index 75062e000c0..de0852810af 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSXORStripedInputStream.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSXORStripedInputStream.java @@ -27,7 +27,7 @@ import org.apache.hadoop.hdfs.server.namenode.ErasureCodingPolicyManager; public class TestDFSXORStripedInputStream extends TestDFSStripedInputStream{ public ErasureCodingPolicy getEcPolicy() { - return ErasureCodingPolicyManager.getPolicyByPolicyID( + return ErasureCodingPolicyManager.getPolicyByID( HdfsConstants.XOR_2_1_POLICY_ID); } } diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSXORStripedOutputStream.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSXORStripedOutputStream.java index 64bddb85607..658c9ba95d7 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSXORStripedOutputStream.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSXORStripedOutputStream.java @@ -29,7 +29,7 @@ public class TestDFSXORStripedOutputStream extends TestDFSStripedOutputStream{ @Override public ErasureCodingPolicy getEcPolicy() { - return ErasureCodingPolicyManager.getPolicyByPolicyID( + return ErasureCodingPolicyManager.getPolicyByID( HdfsConstants.XOR_2_1_POLICY_ID); } } diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSXORStripedOutputStreamWithFailure.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSXORStripedOutputStreamWithFailure.java index ed361a8594a..c97644ecdc3 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSXORStripedOutputStreamWithFailure.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSXORStripedOutputStreamWithFailure.java @@ -30,7 +30,7 @@ public class TestDFSXORStripedOutputStreamWithFailure @Override public ErasureCodingPolicy getEcPolicy() { - return ErasureCodingPolicyManager.getPolicyByPolicyID( + return ErasureCodingPolicyManager.getPolicyByID( HdfsConstants.XOR_2_1_POLICY_ID); } } \ No newline at end of file diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestErasureCodingPolicies.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestErasureCodingPolicies.java index e6a822775d1..4a4027b6eaf 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestErasureCodingPolicies.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestErasureCodingPolicies.java @@ -23,6 +23,7 @@ import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.fs.permission.FsPermission; import org.apache.hadoop.hdfs.protocol.DirectoryListing; +import org.apache.hadoop.hdfs.protocol.HdfsConstants; import org.apache.hadoop.hdfs.protocol.HdfsFileStatus; import org.apache.hadoop.hdfs.server.namenode.ErasureCodingPolicyManager; import org.apache.hadoop.hdfs.server.namenode.FSNamesystem; @@ -34,6 +35,7 @@ import org.apache.hadoop.io.erasurecode.ECSchema; import org.apache.hadoop.security.AccessControlException; import org.apache.hadoop.security.UserGroupInformation; import org.junit.After; +import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -64,6 +66,7 @@ public class TestErasureCodingPolicies { public void setupCluster() throws IOException { conf = new HdfsConfiguration(); conf.setInt(DFSConfigKeys.DFS_BLOCK_SIZE_KEY, BLOCK_SIZE); + DFSTestUtil.enableAllECPolicies(conf); cluster = new MiniDFSCluster.Builder(conf). numDataNodes(1).build(); cluster.waitActive(); @@ -189,6 +192,40 @@ public class TestErasureCodingPolicies { } catch (IOException e) { assertExceptionContains("erasure coding policy for a file", e); } + + // Verify that policies are successfully loaded even when policies + // are disabled + cluster.getConfiguration(0).set( + DFSConfigKeys.DFS_NAMENODE_EC_POLICIES_ENABLED_KEY, ""); + cluster.restartNameNodes(); + cluster.waitActive(); + + // No policies should be enabled after restart + Assert.assertTrue("No policies should be enabled after restart", + fs.getAllErasureCodingPolicies().isEmpty()); + + // Already set directory-level policies should still be in effect + Path disabledPolicy = new Path(dir1, "afterDisabled"); + Assert.assertEquals("Dir does not have policy set", + EC_POLICY, + fs.getErasureCodingPolicy(dir1)); + fs.create(disabledPolicy).close(); + Assert.assertEquals("File did not inherit dir's policy", + EC_POLICY, + fs.getErasureCodingPolicy(disabledPolicy)); + + // Also check loading disabled EC policies from fsimage + fs.setSafeMode(HdfsConstants.SafeModeAction.SAFEMODE_ENTER); + fs.saveNamespace(); + fs.setSafeMode(HdfsConstants.SafeModeAction.SAFEMODE_LEAVE); + cluster.restartNameNodes(); + + Assert.assertEquals("Dir does not have policy set", + EC_POLICY, + fs.getErasureCodingPolicy(dir1)); + Assert.assertEquals("File does not have policy set", + EC_POLICY, + fs.getErasureCodingPolicy(disabledPolicy)); } @Test @@ -310,7 +347,7 @@ public class TestErasureCodingPolicies { + "setting an invalid erasure coding policy"); } catch (Exception e) { assertExceptionContains("Policy 'RS-4-2-128k' does not match " + - "any supported erasure coding policies",e); + "any enabled erasure coding policies", e); } } @@ -320,7 +357,7 @@ public class TestErasureCodingPolicies { .getSystemPolicies(); Collection allECPolicies = fs .getAllErasureCodingPolicies(); - assertTrue("All system policies should be active", + assertTrue("All system policies should be enabled", allECPolicies.containsAll(Arrays.asList(sysECPolicies))); } diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestUnsetAndChangeDirectoryEcPolicy.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestUnsetAndChangeDirectoryEcPolicy.java index 86624cc05b3..ec19a744d54 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestUnsetAndChangeDirectoryEcPolicy.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestUnsetAndChangeDirectoryEcPolicy.java @@ -72,6 +72,7 @@ public class TestUnsetAndChangeDirectoryEcPolicy { CodecUtil.IO_ERASURECODE_CODEC_RS_RAWCODER_KEY, NativeRSRawErasureCoderFactory.class.getCanonicalName()); } + DFSTestUtil.enableAllECPolicies(conf); cluster = new MiniDFSCluster.Builder(conf).numDataNodes( dataBlocks + parityBlocks).build(); cluster.waitActive(); @@ -139,7 +140,7 @@ public class TestUnsetAndChangeDirectoryEcPolicy { final Path ec32FilePath = new Path(childDir, "ec_3_2_file"); final Path ec63FilePath2 = new Path(childDir, "ec_6_3_file_2"); final ErasureCodingPolicy ec32Policy = ErasureCodingPolicyManager - .getPolicyByPolicyID(HdfsConstants.RS_3_2_POLICY_ID); + .getPolicyByID(HdfsConstants.RS_3_2_POLICY_ID); fs.mkdirs(parentDir); fs.setErasureCodingPolicy(parentDir, ecPolicy.getName()); @@ -237,7 +238,7 @@ public class TestUnsetAndChangeDirectoryEcPolicy { final Path ec63FilePath = new Path(rootPath, "ec_6_3_file"); final Path ec32FilePath = new Path(rootPath, "ec_3_2_file"); final ErasureCodingPolicy ec32Policy = ErasureCodingPolicyManager - .getPolicyByPolicyID(HdfsConstants.RS_3_2_POLICY_ID); + .getPolicyByID(HdfsConstants.RS_3_2_POLICY_ID); fs.unsetErasureCodingPolicy(rootPath); fs.setErasureCodingPolicy(rootPath, ecPolicy.getName()); diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestEnabledECPolicies.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestEnabledECPolicies.java new file mode 100644 index 00000000000..dd4ae0b81ae --- /dev/null +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestEnabledECPolicies.java @@ -0,0 +1,151 @@ +/** + * 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 org.apache.hadoop.hdfs.DFSConfigKeys; +import org.apache.hadoop.hdfs.HdfsConfiguration; +import org.apache.hadoop.hdfs.protocol.ErasureCodingPolicy; +import org.apache.hadoop.test.GenericTestUtils; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.Timeout; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +/** + * Test that ErasureCodingPolicyManager correctly parses the set of enabled + * erasure coding policies from configuration and exposes this information. + */ +public class TestEnabledECPolicies { + + private static final ErasureCodingPolicy[] SYSTEM_POLICIES = + ErasureCodingPolicyManager.getSystemPolicies(); + + @Rule + public Timeout testTimeout = new Timeout(60000); + + private void expectInvalidPolicy(String value) { + HdfsConfiguration conf = new HdfsConfiguration(); + conf.set(DFSConfigKeys.DFS_NAMENODE_EC_POLICIES_ENABLED_KEY, + value); + try { + new ErasureCodingPolicyManager(conf); + fail("Expected exception when instantiating ECPolicyManager"); + } catch (IllegalArgumentException e) { + GenericTestUtils.assertExceptionContains("is not a valid policy", e); + } + } + + private void expectValidPolicy(String value, final int numEnabled) throws + Exception { + HdfsConfiguration conf = new HdfsConfiguration(); + conf.set(DFSConfigKeys.DFS_NAMENODE_EC_POLICIES_ENABLED_KEY, + value); + ErasureCodingPolicyManager manager = new ErasureCodingPolicyManager(conf); + assertEquals("Incorrect number of enabled policies", + numEnabled, manager.getEnabledPolicies().length); + } + + @Test + public void testInvalid() throws Exception { + // Test first with an invalid policy + expectInvalidPolicy("not-a-policy"); + // Test with an invalid policy and a valid policy + expectInvalidPolicy("not-a-policy," + ErasureCodingPolicyManager + .getSystemDefaultPolicy().getName()); + // Test with a valid and an invalid policy + expectInvalidPolicy(ErasureCodingPolicyManager + .getSystemDefaultPolicy().getName() + ", not-a-policy"); + // Some more invalid values + expectInvalidPolicy("not-a-policy, "); + expectInvalidPolicy(" ,not-a-policy, "); + } + + @Test + public void testValid() throws Exception { + String ecPolicyName = ErasureCodingPolicyManager.getSystemDefaultPolicy() + .getName(); + expectValidPolicy(ecPolicyName, 1); + expectValidPolicy(ecPolicyName + ", ", 1); + expectValidPolicy(",", 0); + } + + @Test + public void testGetPolicies() throws Exception { + ErasureCodingPolicy[] enabledPolicies; + // Enable no policies + enabledPolicies = new ErasureCodingPolicy[] + {SYSTEM_POLICIES[1], SYSTEM_POLICIES[2]}; + testGetPolicies(enabledPolicies); + + // Enable one policy + enabledPolicies = new ErasureCodingPolicy[] + {SYSTEM_POLICIES[1]}; + testGetPolicies(enabledPolicies); + + // Enable two policies + enabledPolicies = new ErasureCodingPolicy[] + {SYSTEM_POLICIES[1], SYSTEM_POLICIES[2]}; + testGetPolicies(enabledPolicies); + } + + private void testGetPolicies(ErasureCodingPolicy[] enabledPolicies) + throws Exception { + HdfsConfiguration conf = new HdfsConfiguration(); + conf.set(DFSConfigKeys.DFS_NAMENODE_EC_POLICIES_ENABLED_KEY, + Arrays.asList(enabledPolicies).stream() + .map(ErasureCodingPolicy::getName) + .collect(Collectors.joining(", "))); + ErasureCodingPolicyManager manager = new ErasureCodingPolicyManager(conf); + + // Check that returned values are unique + Set found = new HashSet<>(); + for (ErasureCodingPolicy p : manager.getEnabledPolicies()) { + Assert.assertFalse("Duplicate policy name found: " + p.getName(), + found.contains(p.getName())); + found.add(p.getName()); + } + // Check that the policies specified in conf are found + for (ErasureCodingPolicy p: enabledPolicies) { + Assert.assertTrue("Did not find specified EC policy " + p.getName(), + found.contains(p.getName())); + } + Assert.assertEquals(enabledPolicies.length, found.size()); + // Check that getEnabledPolicyByName only returns enabled policies + for (ErasureCodingPolicy p: SYSTEM_POLICIES) { + if (found.contains(p.getName())) { + // Enabled policy should be present + Assert.assertNotNull( + "getEnabledPolicyByName did not find enabled policy" + p.getName(), + manager.getEnabledPolicyByName(p.getName())); + } else { + // Disabled policy should not be present + Assert.assertNull( + "getEnabledPolicyByName found disabled policy " + p.getName(), + manager.getEnabledPolicyByName(p.getName())); + } + } + } +} 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 index 57416ed16d7..e1861643f3f 100644 --- 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 @@ -77,7 +77,7 @@ public class TestFSImage { private static final String HADOOP_2_7_ZER0_BLOCK_SIZE_TGZ = "image-with-zero-block-size.tar.gz"; private static final ErasureCodingPolicy testECPolicy = - ErasureCodingPolicyManager.getPolicyByPolicyID( + ErasureCodingPolicyManager.getPolicyByID( HdfsConstants.RS_10_4_POLICY_ID); @Test @@ -240,6 +240,7 @@ public class TestFSImage { @Test public void testSaveAndLoadStripedINodeFile() throws IOException{ Configuration conf = new Configuration(); + DFSTestUtil.enableAllECPolicies(conf); MiniDFSCluster cluster = null; try { cluster = new MiniDFSCluster.Builder(conf).build(); @@ -260,6 +261,7 @@ public class TestFSImage { public void testSaveAndLoadStripedINodeFileUC() throws IOException { // construct a INode with StripedBlock for saving and loading Configuration conf = new Configuration(); + DFSTestUtil.enableAllECPolicies(conf); MiniDFSCluster cluster = null; try { cluster = new MiniDFSCluster.Builder(conf).build(); @@ -459,6 +461,7 @@ public class TestFSImage { final int BLOCK_SIZE = 8 * 1024 * 1024; Configuration conf = new HdfsConfiguration(); conf.setLong(DFSConfigKeys.DFS_BLOCK_SIZE_KEY, BLOCK_SIZE); + DFSTestUtil.enableAllECPolicies(conf); MiniDFSCluster cluster = null; try { cluster = new MiniDFSCluster.Builder(conf).numDataNodes(GROUP_SIZE) @@ -468,7 +471,7 @@ public class TestFSImage { Path parentDir = new Path("/ec-10-4"); Path childDir = new Path(parentDir, "ec-3-2"); ErasureCodingPolicy ec32Policy = ErasureCodingPolicyManager - .getPolicyByPolicyID(HdfsConstants.RS_3_2_POLICY_ID); + .getPolicyByID(HdfsConstants.RS_3_2_POLICY_ID); // Create directories and files fs.mkdirs(parentDir); @@ -516,7 +519,7 @@ public class TestFSImage { // check the information of file_3_2 inode = fsn.dir.getINode(file_3_2.toString()).asFile(); assertTrue(inode.isStriped()); - assertEquals(ErasureCodingPolicyManager.getPolicyByPolicyID( + assertEquals(ErasureCodingPolicyManager.getPolicyByID( HdfsConstants.RS_3_2_POLICY_ID).getId(), inode.getErasureCodingPolicyID()); blks = inode.getBlocks(); 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 e52d5365981..0656249cfca 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 @@ -124,6 +124,10 @@ public class TestOfflineImageViewer { tempDir = Files.createTempDir(); MiniDFSCluster cluster = null; try { + final ErasureCodingPolicy ecPolicy = + ErasureCodingPolicyManager.getPolicyByID( + HdfsConstants.XOR_2_1_POLICY_ID); + Configuration conf = new Configuration(); conf.setLong( DFSConfigKeys.DFS_NAMENODE_DELEGATION_TOKEN_MAX_LIFETIME_KEY, 10000); @@ -134,6 +138,8 @@ public class TestOfflineImageViewer { conf.setBoolean(DFSConfigKeys.DFS_NAMENODE_ACLS_ENABLED_KEY, true); conf.set(CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTH_TO_LOCAL, "RULE:[2:$1@$0](JobTracker@.*FOO.COM)s/@.*//" + "DEFAULT"); + conf.set(DFSConfigKeys.DFS_NAMENODE_EC_POLICIES_ENABLED_KEY, + ecPolicy.getName()); cluster = new MiniDFSCluster.Builder(conf).numDataNodes(3).build(); cluster.waitActive(); DistributedFileSystem hdfs = cluster.getFileSystem(); @@ -233,9 +239,6 @@ public class TestOfflineImageViewer { Path ecDir = new Path("/ec"); hdfs.mkdirs(ecDir); dirCount++; - ErasureCodingPolicy ecPolicy = - ErasureCodingPolicyManager.getPolicyByPolicyID( - HdfsConstants.XOR_2_1_POLICY_ID); hdfs.getClient().setErasureCodingPolicy(ecDir.toString(), ecPolicy.getName()); writtenFiles.put(ecDir.toString(), hdfs.getFileStatus(ecDir)); @@ -409,7 +412,7 @@ public class TestOfflineImageViewer { Assert.assertEquals("INode '" + currentInodeName + "' has unexpected EC Policy!", Byte.parseByte(currentECPolicy), - ErasureCodingPolicyManager.getPolicyByPolicyID( + ErasureCodingPolicyManager.getPolicyByID( HdfsConstants.XOR_2_1_POLICY_ID).getId()); Assert.assertEquals("INode '" + currentInodeName + "' has unexpected replication!", diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/resources/testErasureCodingConf.xml b/hadoop-hdfs-project/hadoop-hdfs/src/test/resources/testErasureCodingConf.xml index 278963a72d6..8dda6c1af22 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/resources/testErasureCodingConf.xml +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/resources/testErasureCodingConf.xml @@ -119,7 +119,7 @@ SubstringComparator - Get the list of supported erasure coding policies + Get the list of enabled erasure coding policies SubstringComparator @@ -359,7 +359,24 @@ SubstringComparator - Policy 'invalidpolicy' does not match any supported erasure coding policies. + Policy 'invalidpolicy' does not match any enabled erasure coding policies + + + + + + setPolicy : illegal parameters - RS-10-4-64k + + -fs NAMENODE -mkdir /ecdir + -fs NAMENODE -setPolicy -policy RS-10-4-64k -path /ecdir + + + -fs NAMENODE -rmdir /ecdir + + + + SubstringComparator + Policy 'RS-10-4-64k' does not match any enabled erasure coding policies From 570827a819c586b31e88621a9bb1d8118d3c7df3 Mon Sep 17 00:00:00 2001 From: John Zhuge Date: Wed, 8 Mar 2017 23:50:15 -0800 Subject: [PATCH 040/188] HADOOP-14052. Fix dead link in KMS document. Contributed by Christina Vu. Change-Id: I7093f443d93927184196f62f02cc106a2c89e9cf --- hadoop-common-project/hadoop-kms/src/site/markdown/index.md.vm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hadoop-common-project/hadoop-kms/src/site/markdown/index.md.vm b/hadoop-common-project/hadoop-kms/src/site/markdown/index.md.vm index c1f9b13ba5b..4573b067856 100644 --- a/hadoop-common-project/hadoop-kms/src/site/markdown/index.md.vm +++ b/hadoop-common-project/hadoop-kms/src/site/markdown/index.md.vm @@ -956,7 +956,7 @@ $H4 Re-encrypt Encrypted Key With The Latest KeyVersion This command takes a previously generated encrypted key, and re-encrypts it using the latest KeyVersion encryption key in the KeyProvider. If the latest KeyVersion is the same as the one used to generate the encrypted key, the same encrypted key is returned. -This is usually useful after a [Rollover](Rollover_Key) of an encryption key. Re-encrypting the encrypted key will allow it to be encrypted using the latest version of the encryption key, but still with the same key material and initialization vector. +This is usually useful after a [Rollover](#Rollover_Key) of an encryption key. Re-encrypting the encrypted key will allow it to be encrypted using the latest version of the encryption key, but still with the same key material and initialization vector. *REQUEST:* From 385d2cb777a0272ac20c62336c944fad295d5d12 Mon Sep 17 00:00:00 2001 From: Masatake Iwasaki Date: Thu, 9 Mar 2017 13:30:33 +0900 Subject: [PATCH 041/188] HDFS-11499. Decommissioning stuck because of failing recovery. Contributed by Lukas Majercak and Manoj Govindassamy. --- .../server/blockmanagement/BlockManager.java | 10 +++- .../apache/hadoop/hdfs/TestDecommission.java | 48 +++++++++++++++++ .../hadoop/hdfs/TestMaintenanceState.java | 51 +++++++++++++++++++ 3 files changed, 108 insertions(+), 1 deletion(-) diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/blockmanagement/BlockManager.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/blockmanagement/BlockManager.java index 9ec28f9c02f..5dc40fa10e7 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/blockmanagement/BlockManager.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/blockmanagement/BlockManager.java @@ -891,7 +891,15 @@ public class BlockManager implements BlockStatsMXBean { lastBlock.getUnderConstructionFeature() .updateStorageScheduledSize((BlockInfoStriped) lastBlock); } - if (hasMinStorage(lastBlock)) { + + // Count replicas on decommissioning nodes, as these will not be + // decommissioned unless recovery/completing last block has finished + NumberReplicas numReplicas = countNodes(lastBlock); + int numUsableReplicas = numReplicas.liveReplicas() + + numReplicas.decommissioning() + + numReplicas.liveEnteringMaintenanceReplicas(); + + if (hasMinStorage(lastBlock, numUsableReplicas)) { if (committed) { addExpectedReplicasToPending(lastBlock); } diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDecommission.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDecommission.java index 94e894619d2..dc0edccdb1a 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDecommission.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDecommission.java @@ -33,6 +33,7 @@ import java.util.concurrent.ExecutionException; import com.google.common.base.Supplier; import com.google.common.collect.Lists; import org.apache.hadoop.fs.BlockLocation; +import org.apache.hadoop.fs.CommonConfigurationKeys; import org.apache.hadoop.fs.FSDataOutputStream; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; @@ -646,6 +647,53 @@ public class TestDecommission extends AdminStatesBaseTest { fdos.close(); } + + @Test(timeout = 360000) + public void testDecommissionWithOpenFileAndBlockRecovery() + throws IOException, InterruptedException { + startCluster(1, 6); + getCluster().waitActive(); + + Path file = new Path("/testRecoveryDecommission"); + + // Create a file and never close the output stream to trigger recovery + DistributedFileSystem dfs = getCluster().getFileSystem(); + FSDataOutputStream out = dfs.create(file, true, + getConf().getInt(CommonConfigurationKeys.IO_FILE_BUFFER_SIZE_KEY, 4096), + (short) 3, blockSize); + + // Write data to the file + long writtenBytes = 0; + while (writtenBytes < fileSize) { + out.writeLong(writtenBytes); + writtenBytes += 8; + } + out.hsync(); + + DatanodeInfo[] lastBlockLocations = NameNodeAdapter.getBlockLocations( + getCluster().getNameNode(), "/testRecoveryDecommission", 0, fileSize) + .getLastLocatedBlock().getLocations(); + + // Decommission all nodes of the last block + ArrayList toDecom = new ArrayList<>(); + for (DatanodeInfo dnDecom : lastBlockLocations) { + toDecom.add(dnDecom.getXferAddr()); + } + initExcludeHosts(toDecom); + refreshNodes(0); + + // Make sure hard lease expires to trigger replica recovery + getCluster().setLeasePeriod(300L, 300L); + Thread.sleep(2 * BLOCKREPORT_INTERVAL_MSEC); + + for (DatanodeInfo dnDecom : lastBlockLocations) { + DatanodeInfo datanode = NameNodeAdapter.getDatanode( + getCluster().getNamesystem(), dnDecom); + waitNodeState(datanode, AdminStates.DECOMMISSIONED); + } + + assertEquals(dfs.getFileStatus(file).getLen(), writtenBytes); + } /** * Tests restart of namenode while datanode hosts are added to exclude file diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestMaintenanceState.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestMaintenanceState.java index 24321533d13..a37bdb8cb05 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestMaintenanceState.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestMaintenanceState.java @@ -31,6 +31,8 @@ import java.util.List; import java.util.Map; import com.google.common.collect.Lists; +import org.apache.hadoop.fs.FSDataOutputStream; +import org.apache.hadoop.hdfs.server.namenode.NameNodeAdapter; import org.junit.Assert; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -41,8 +43,10 @@ import org.apache.hadoop.hdfs.protocol.DatanodeInfo; import org.apache.hadoop.hdfs.protocol.DatanodeInfo.AdminStates; import org.apache.hadoop.hdfs.protocol.HdfsConstants.DatanodeReportType; import org.apache.hadoop.hdfs.protocol.LocatedBlock; +import org.apache.hadoop.hdfs.protocol.LocatedBlocks; import org.apache.hadoop.hdfs.server.blockmanagement.BlockManager; import org.apache.hadoop.hdfs.server.blockmanagement.DatanodeStorageInfo; +import org.apache.hadoop.hdfs.server.datanode.DataNode; import org.apache.hadoop.hdfs.server.namenode.FSNamesystem; import org.apache.hadoop.util.Time; import org.junit.Test; @@ -940,6 +944,53 @@ public class TestMaintenanceState extends AdminStatesBaseTest { cleanupFile(fileSys, file); } + @Test(timeout = 120000) + public void testFileCloseAfterEnteringMaintenance() throws Exception { + LOG.info("Starting testFileCloseAfterEnteringMaintenance"); + int expirationInMs = 30 * 1000; + int numDataNodes = 3; + int numNameNodes = 1; + getConf().setInt(DFSConfigKeys.DFS_NAMENODE_REPLICATION_MIN_KEY, 2); + + startCluster(numNameNodes, numDataNodes); + getCluster().waitActive(); + + FSNamesystem fsn = getCluster().getNameNode().getNamesystem(); + List hosts = new ArrayList<>(); + for (DataNode dn : getCluster().getDataNodes()) { + hosts.add(dn.getDisplayName()); + putNodeInService(0, dn.getDatanodeUuid()); + } + assertEquals(numDataNodes, fsn.getNumLiveDataNodes()); + + Path openFile = new Path("/testClosingFileInMaintenance.dat"); + // Lets write 2 blocks of data to the openFile + writeFile(getCluster().getFileSystem(), openFile, (short) 3); + + // Lets write some more data and keep the file open + FSDataOutputStream fsDataOutputStream = getCluster().getFileSystem() + .append(openFile); + byte[] bytes = new byte[1024]; + fsDataOutputStream.write(bytes); + fsDataOutputStream.hsync(); + + LocatedBlocks lbs = NameNodeAdapter.getBlockLocations( + getCluster().getNameNode(0), openFile.toString(), 0, 3 * blockSize); + DatanodeInfo[] dnInfos4LastBlock = lbs.getLastLocatedBlock().getLocations(); + + // Request maintenance for DataNodes 1 and 2 which has the last block. + takeNodeOutofService(0, + Lists.newArrayList(dnInfos4LastBlock[0].getDatanodeUuid(), + dnInfos4LastBlock[1].getDatanodeUuid()), + Time.now() + expirationInMs, + null, null, AdminStates.ENTERING_MAINTENANCE); + + // Closing the file should succeed even when the + // last blocks' nodes are entering maintenance. + fsDataOutputStream.close(); + cleanupFile(getCluster().getFileSystem(), openFile); + } + static String getFirstBlockFirstReplicaUuid(FileSystem fileSys, Path name) throws IOException { DatanodeInfo[] nodes = getFirstBlockReplicasDatanodeInfos(fileSys, name); From 822a74f2ae955ea0893cc02fb36ceb49ceba8014 Mon Sep 17 00:00:00 2001 From: Daniel Templeton Date: Thu, 9 Mar 2017 12:12:47 -0800 Subject: [PATCH 042/188] YARN-6300. NULL_UPDATE_REQUESTS is redundant in TestFairScheduler (Contributed by Yuanbo Liu via Daniel Templeton) --- .../resourcemanager/scheduler/fair/TestFairScheduler.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/fair/TestFairScheduler.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/fair/TestFairScheduler.java index 31dd7fe5ad6..028eea61829 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/fair/TestFairScheduler.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/fair/TestFairScheduler.java @@ -96,7 +96,6 @@ import org.apache.hadoop.yarn.server.resourcemanager.rmnode.RMNodeResourceUpdate import org.apache.hadoop.yarn.server.resourcemanager.scheduler.AbstractYarnScheduler; -import org.apache.hadoop.yarn.server.resourcemanager.scheduler.ContainerUpdates; import org.apache.hadoop.yarn.server.resourcemanager.scheduler.QueueMetrics; import org.apache.hadoop.yarn.server.resourcemanager.scheduler.SchedulerApplicationAttempt; import org.apache.hadoop.yarn.server.resourcemanager.scheduler.SchedulerNode; @@ -119,7 +118,6 @@ import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; -import org.mockito.Mock; import org.mockito.Mockito; import org.xml.sax.SAXException; @@ -130,8 +128,6 @@ public class TestFairScheduler extends FairSchedulerTestBase { private final int GB = 1024; private final static String ALLOC_FILE = new File(TEST_DIR, "test-queues").getAbsolutePath(); - private final static ContainerUpdates NULL_UPDATE_REQUESTS = - new ContainerUpdates(); @Before public void setUp() throws IOException { From e96a0b8c92b46aed7c1f5ccec13abc6c1043edba Mon Sep 17 00:00:00 2001 From: Sidharta S Date: Thu, 9 Mar 2017 16:22:19 -0800 Subject: [PATCH 043/188] YARN-5669. Add support for docker pull command (Contribtued by luhuichun) --- .../runtime/docker/DockerPullCommand.java | 31 ++++++++++++ .../runtime/docker/TestDockerPullCommand.java | 49 +++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/linux/runtime/docker/DockerPullCommand.java create mode 100644 hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/linux/runtime/docker/TestDockerPullCommand.java diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/linux/runtime/docker/DockerPullCommand.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/linux/runtime/docker/DockerPullCommand.java new file mode 100644 index 00000000000..351e09ebc00 --- /dev/null +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/linux/runtime/docker/DockerPullCommand.java @@ -0,0 +1,31 @@ +/* + * 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.yarn.server.nodemanager.containermanager.linux.runtime.docker; + +/** + * Encapsulates the docker pull command and its command + * line arguments. + */ +public class DockerPullCommand extends DockerCommand { + private static final String PULL_COMMAND = "pull"; + + public DockerPullCommand(String imageName) { + super(PULL_COMMAND); + super.addCommandArguments(imageName); + } + +} diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/linux/runtime/docker/TestDockerPullCommand.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/linux/runtime/docker/TestDockerPullCommand.java new file mode 100644 index 00000000000..89157ff751a --- /dev/null +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/linux/runtime/docker/TestDockerPullCommand.java @@ -0,0 +1,49 @@ +/* + * 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.yarn.server.nodemanager.containermanager.linux.runtime.docker; + +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * Tests the docker pull command and its command + * line arguments. + */ +public class TestDockerPullCommand { + private DockerPullCommand dockerPullCommand; + + private static final String IMAGE_NAME = "foo"; + + @Before + public void setup() { + dockerPullCommand = new DockerPullCommand(IMAGE_NAME); + } + + @Test + public void testGetCommandOption() { + assertEquals("pull", dockerPullCommand.getCommandOption()); + } + + @Test + public void testGetCommandWithArguments() { + assertEquals("pull foo", dockerPullCommand.getCommandWithArguments()); + } + + +} From 819808a016e16325502169e0091a16a6b2ae5387 Mon Sep 17 00:00:00 2001 From: Andrew Wang Date: Thu, 9 Mar 2017 17:29:11 -0800 Subject: [PATCH 044/188] HDFS-11506. Move ErasureCodingPolicyManager#getSystemDefaultPolicy to test code. Contributed by Manoj Govindassamy. --- .../namenode/ErasureCodingPolicyManager.java | 10 ------ .../org/apache/hadoop/hdfs/DFSTestUtil.java | 2 +- .../hdfs/ErasureCodeBenchmarkThroughput.java | 5 +-- .../hadoop/hdfs/StripedFileTestUtil.java | 12 +++++++ .../hdfs/TestDFSStripedInputStream.java | 3 +- .../hdfs/TestDFSStripedOutputStream.java | 3 +- ...TestDFSStripedOutputStreamWithFailure.java | 3 +- .../hdfs/TestDecommissionWithStriped.java | 5 ++- .../hdfs/TestErasureCodingPolicies.java | 8 ++--- .../TestErasureCodingPolicyWithSnapshot.java | 3 +- .../apache/hadoop/hdfs/TestFileChecksum.java | 5 ++- .../hdfs/TestFileStatusWithECPolicy.java | 3 +- .../hadoop/hdfs/TestLeaseRecoveryStriped.java | 3 +- .../hdfs/TestReadStripedFileWithDecoding.java | 5 ++- .../TestReadStripedFileWithMissingBlocks.java | 3 +- .../hdfs/TestReconstructStripedFile.java | 7 ++-- .../hdfs/TestSafeModeWithStripedFile.java | 5 ++- .../TestUnsetAndChangeDirectoryEcPolicy.java | 3 +- .../hadoop/hdfs/TestWriteReadStripedFile.java | 5 ++- .../hdfs/TestWriteStripedFileWithFailure.java | 5 ++- .../hadoop/hdfs/protocolPB/TestPBHelper.java | 12 +++---- .../hdfs/server/balancer/TestBalancer.java | 5 ++- .../blockmanagement/TestBlockInfoStriped.java | 4 +-- .../TestBlockTokenWithDFSStriped.java | 6 ++-- .../TestLowRedundancyBlockQueues.java | 4 +-- ...nstructStripedBlocksWithRackAwareness.java | 10 +++--- .../TestSequentialBlockGroupId.java | 6 ++-- .../TestSortLocatedStripedBlock.java | 4 +-- .../server/datanode/TestBlockRecovery.java | 3 +- .../TestDataNodeErasureCodingMetrics.java | 5 ++- .../hadoop/hdfs/server/mover/TestMover.java | 5 ++- .../TestAddOverReplicatedStripedBlocks.java | 6 ++-- .../namenode/TestAddStripedBlockInFBR.java | 5 +-- .../server/namenode/TestAddStripedBlocks.java | 7 ++-- .../namenode/TestEnabledECPolicies.java | 12 +++---- .../server/namenode/TestFSEditLogLoader.java | 3 +- .../hadoop/hdfs/server/namenode/TestFsck.java | 33 ++++++++----------- .../server/namenode/TestNameNodeMXBean.java | 12 +++---- .../namenode/TestQuotaWithStripedBlocks.java | 3 +- .../TestReconstructStripedBlocks.java | 6 ++-- .../server/namenode/TestStripedINodeFile.java | 5 +-- ...stOfflineImageViewerWithStripedBlocks.java | 8 ++--- 42 files changed, 121 insertions(+), 141 deletions(-) diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/ErasureCodingPolicyManager.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/ErasureCodingPolicyManager.java index 02cbbdf38fd..29af2072995 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/ErasureCodingPolicyManager.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/ErasureCodingPolicyManager.java @@ -129,16 +129,6 @@ public final class ErasureCodingPolicyManager { return SYS_POLICIES; } - /** - * Get system-wide default policy, which can be used by default - * when no policy is specified for a path. - * @return ecPolicy - */ - public static ErasureCodingPolicy getSystemDefaultPolicy() { - // make this configurable? - return SYS_POLICY1; - } - /** * Get a policy by policy ID. * @return ecPolicy, or null if not found diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/DFSTestUtil.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/DFSTestUtil.java index 7bf5cdcf6ec..13291952a9e 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/DFSTestUtil.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/DFSTestUtil.java @@ -1910,7 +1910,7 @@ public class DFSTestUtil { Path dir, int numBlocks, int numStripesPerBlk, boolean toMkdir) throws Exception { createStripedFile(cluster, file, dir, numBlocks, numStripesPerBlk, - toMkdir, ErasureCodingPolicyManager.getSystemDefaultPolicy()); + toMkdir, StripedFileTestUtil.getDefaultECPolicy()); } /** diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/ErasureCodeBenchmarkThroughput.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/ErasureCodeBenchmarkThroughput.java index d1a7569fa02..20ddfd18652 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/ErasureCodeBenchmarkThroughput.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/ErasureCodeBenchmarkThroughput.java @@ -28,7 +28,6 @@ import org.apache.hadoop.fs.Path; import org.apache.hadoop.fs.PathFilter; import org.apache.hadoop.hdfs.client.HdfsClientConfigKeys; import org.apache.hadoop.hdfs.protocol.ErasureCodingPolicy; -import org.apache.hadoop.hdfs.server.namenode.ErasureCodingPolicyManager; import org.apache.hadoop.util.StopWatch; import org.apache.hadoop.util.Tool; import org.apache.hadoop.util.ToolRunner; @@ -42,9 +41,7 @@ import java.util.Random; import java.util.concurrent.Callable; import java.util.concurrent.CompletionService; import java.util.concurrent.ExecutorCompletionService; -import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; /** @@ -81,7 +78,7 @@ public class ErasureCodeBenchmarkThroughput private static final String EC_FILE_BASE = "ec-file-"; private static final String TMP_FILE_SUFFIX = ".tmp"; private static final ErasureCodingPolicy ecPolicy = - ErasureCodingPolicyManager.getSystemDefaultPolicy(); + StripedFileTestUtil.getDefaultECPolicy(); private static final byte[] data = new byte[BUFFER_SIZE_MB * 1024 * 1024]; static { diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/StripedFileTestUtil.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/StripedFileTestUtil.java index 520d0e3134a..8008ed3396e 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/StripedFileTestUtil.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/StripedFileTestUtil.java @@ -29,9 +29,11 @@ import org.apache.hadoop.hdfs.client.impl.BlockReaderTestUtil; import org.apache.hadoop.hdfs.protocol.DatanodeInfo; import org.apache.hadoop.hdfs.protocol.ErasureCodingPolicy; import org.apache.hadoop.hdfs.protocol.ExtendedBlock; +import org.apache.hadoop.hdfs.protocol.HdfsConstants; import org.apache.hadoop.hdfs.protocol.LocatedBlock; import org.apache.hadoop.hdfs.protocol.LocatedBlocks; import org.apache.hadoop.hdfs.protocol.LocatedStripedBlock; +import org.apache.hadoop.hdfs.server.namenode.ErasureCodingPolicyManager; import org.apache.hadoop.hdfs.util.StripedBlockUtil; import org.apache.hadoop.hdfs.web.WebHdfsFileSystem.WebHdfsInputStream; import org.apache.hadoop.io.IOUtils; @@ -558,4 +560,14 @@ public class StripedFileTestUtil { throws IOException { return fs.getClient().getLocatedBlocks(file.toString(), 0, Long.MAX_VALUE); } + + /** + * Get system-wide default Erasure Coding Policy, which can be + * used by default when no policy is specified for a path. + * @return ErasureCodingPolicy + */ + public static ErasureCodingPolicy getDefaultECPolicy() { + return ErasureCodingPolicyManager.getPolicyByID( + HdfsConstants.RS_6_3_POLICY_ID); + } } diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSStripedInputStream.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSStripedInputStream.java index 29ef6943af3..68fde95ca8e 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSStripedInputStream.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSStripedInputStream.java @@ -29,7 +29,6 @@ import org.apache.hadoop.hdfs.protocol.LocatedStripedBlock; import org.apache.hadoop.hdfs.server.datanode.DataNode; import org.apache.hadoop.hdfs.server.datanode.DataNodeTestUtils; import org.apache.hadoop.hdfs.server.datanode.SimulatedFSDataset; -import org.apache.hadoop.hdfs.server.namenode.ErasureCodingPolicyManager; import org.apache.hadoop.hdfs.util.StripedBlockUtil; import org.apache.hadoop.io.erasurecode.CodecUtil; import org.apache.hadoop.io.erasurecode.ErasureCodeNative; @@ -76,7 +75,7 @@ public class TestDFSStripedInputStream { public Timeout globalTimeout = new Timeout(300000); public ErasureCodingPolicy getEcPolicy() { - return ErasureCodingPolicyManager.getSystemDefaultPolicy(); + return StripedFileTestUtil.getDefaultECPolicy(); } @Before diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSStripedOutputStream.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSStripedOutputStream.java index 675f0422c14..ebdecfd6406 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSStripedOutputStream.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSStripedOutputStream.java @@ -26,7 +26,6 @@ import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; import org.apache.hadoop.hdfs.protocol.DatanodeInfo; import org.apache.hadoop.hdfs.protocol.ErasureCodingPolicy; -import org.apache.hadoop.hdfs.server.namenode.ErasureCodingPolicyManager; import org.apache.hadoop.io.erasurecode.CodecUtil; import org.apache.hadoop.io.erasurecode.ErasureCodeNative; import org.apache.hadoop.io.erasurecode.rawcoder.NativeRSRawErasureCoderFactory; @@ -62,7 +61,7 @@ public class TestDFSStripedOutputStream { public Timeout globalTimeout = new Timeout(300000); public ErasureCodingPolicy getEcPolicy() { - return ErasureCodingPolicyManager.getSystemDefaultPolicy(); + return StripedFileTestUtil.getDefaultECPolicy(); } @Before diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSStripedOutputStreamWithFailure.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSStripedOutputStreamWithFailure.java index 9bd29231fd6..c66c7f27b71 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSStripedOutputStreamWithFailure.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSStripedOutputStreamWithFailure.java @@ -36,7 +36,6 @@ import org.apache.hadoop.hdfs.security.token.block.SecurityTestUtil; import org.apache.hadoop.hdfs.server.blockmanagement.BlockManager; import org.apache.hadoop.hdfs.server.blockmanagement.BlockPlacementPolicy; import org.apache.hadoop.hdfs.server.datanode.DataNode; -import org.apache.hadoop.hdfs.server.namenode.ErasureCodingPolicyManager; import org.apache.hadoop.hdfs.server.namenode.NameNode; import org.apache.hadoop.io.erasurecode.CodecUtil; import org.apache.hadoop.io.erasurecode.ErasureCodeNative; @@ -89,7 +88,7 @@ public class TestDFSStripedOutputStreamWithFailure { 9 * DFSConfigKeys.DFS_BYTES_PER_CHECKSUM_DEFAULT + 1; public ErasureCodingPolicy getEcPolicy() { - return ErasureCodingPolicyManager.getSystemDefaultPolicy(); + return StripedFileTestUtil.getDefaultECPolicy(); } /* diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDecommissionWithStriped.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDecommissionWithStriped.java index f8ed0c3f697..a5df85fcb90 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDecommissionWithStriped.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDecommissionWithStriped.java @@ -47,7 +47,6 @@ import org.apache.hadoop.hdfs.protocol.LocatedBlock; import org.apache.hadoop.hdfs.protocol.LocatedStripedBlock; import org.apache.hadoop.hdfs.server.blockmanagement.BlockManager; import org.apache.hadoop.hdfs.server.datanode.DataNode; -import org.apache.hadoop.hdfs.server.namenode.ErasureCodingPolicyManager; import org.apache.hadoop.hdfs.server.namenode.FSNamesystem; import org.apache.hadoop.hdfs.server.namenode.NameNode; import org.apache.hadoop.hdfs.server.namenode.NameNodeAdapter; @@ -83,7 +82,7 @@ public class TestDecommissionWithStriped { private MiniDFSCluster cluster; private DistributedFileSystem dfs; private final ErasureCodingPolicy ecPolicy = - ErasureCodingPolicyManager.getSystemDefaultPolicy(); + StripedFileTestUtil.getDefaultECPolicy(); private int numDNs; private final int cellSize = ecPolicy.getCellSize(); private final int dataBlocks = ecPolicy.getNumDataUnits(); @@ -143,7 +142,7 @@ public class TestDecommissionWithStriped { dfs.mkdirs(ecDir); dfs.setErasureCodingPolicy(ecDir, - ErasureCodingPolicyManager.getSystemDefaultPolicy().getName()); + StripedFileTestUtil.getDefaultECPolicy().getName()); } @After diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestErasureCodingPolicies.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestErasureCodingPolicies.java index 4a4027b6eaf..5ba4403d2ed 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestErasureCodingPolicies.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestErasureCodingPolicies.java @@ -56,7 +56,7 @@ public class TestErasureCodingPolicies { private DistributedFileSystem fs; private static final int BLOCK_SIZE = 1024; private static final ErasureCodingPolicy EC_POLICY = - ErasureCodingPolicyManager.getSystemDefaultPolicy(); + StripedFileTestUtil.getDefaultECPolicy(); private FSNamesystem namesystem; @Rule @@ -95,7 +95,7 @@ public class TestErasureCodingPolicies { // set ec policy on dir fs.setErasureCodingPolicy(dir, - ErasureCodingPolicyManager.getSystemDefaultPolicy().getName()); + StripedFileTestUtil.getDefaultECPolicy().getName()); // create a file which should be using ec final Path ecSubDir = new Path(dir, "ecSubDir"); final Path ecFile = new Path(ecSubDir, "ecFile"); @@ -270,7 +270,7 @@ public class TestErasureCodingPolicies { final Path testDir = new Path("/ec"); fs.mkdir(testDir, FsPermission.getDirDefault()); fs.setErasureCodingPolicy(testDir, - ErasureCodingPolicyManager.getSystemDefaultPolicy().getName()); + StripedFileTestUtil.getDefaultECPolicy().getName()); final Path fooFile = new Path(testDir, "foo"); // create ec file with replication=0 fs.create(fooFile, FsPermission.getFileDefault(), true, @@ -292,7 +292,7 @@ public class TestErasureCodingPolicies { assertNull(fs.getClient().getFileInfo(src).getErasureCodingPolicy()); // dir EC policy after setting ErasureCodingPolicy sysDefaultECPolicy = - ErasureCodingPolicyManager.getSystemDefaultPolicy(); + StripedFileTestUtil.getDefaultECPolicy(); fs.getClient().setErasureCodingPolicy(src, sysDefaultECPolicy.getName()); verifyErasureCodingInfo(src, sysDefaultECPolicy); fs.create(new Path(ecDir, "child1")).close(); diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestErasureCodingPolicyWithSnapshot.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestErasureCodingPolicyWithSnapshot.java index 6cf4ef42c72..4833c24d2e6 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestErasureCodingPolicyWithSnapshot.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestErasureCodingPolicyWithSnapshot.java @@ -28,7 +28,6 @@ import org.apache.hadoop.fs.Path; import org.apache.hadoop.fs.permission.FsPermission; import org.apache.hadoop.hdfs.protocol.ErasureCodingPolicy; import org.apache.hadoop.hdfs.protocol.HdfsConstants.SafeModeAction; -import org.apache.hadoop.hdfs.server.namenode.ErasureCodingPolicyManager; import org.apache.hadoop.util.ToolRunner; import org.junit.After; import org.junit.Before; @@ -41,7 +40,7 @@ public class TestErasureCodingPolicyWithSnapshot { private final static int SUCCESS = 0; private final ErasureCodingPolicy sysDefaultPolicy = - ErasureCodingPolicyManager.getSystemDefaultPolicy(); + StripedFileTestUtil.getDefaultECPolicy(); private final short groupSize = (short) ( sysDefaultPolicy.getNumDataUnits() + sysDefaultPolicy.getNumParityUnits()); diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestFileChecksum.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestFileChecksum.java index 8ae176ffef3..7f63e18abd7 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestFileChecksum.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestFileChecksum.java @@ -26,7 +26,6 @@ import org.apache.hadoop.hdfs.protocol.ErasureCodingPolicy; import org.apache.hadoop.hdfs.protocol.LocatedBlock; import org.apache.hadoop.hdfs.protocol.LocatedBlocks; import org.apache.hadoop.hdfs.server.datanode.DataNode; -import org.apache.hadoop.hdfs.server.namenode.ErasureCodingPolicyManager; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -47,7 +46,7 @@ public class TestFileChecksum { private static final Logger LOG = LoggerFactory .getLogger(TestFileChecksum.class); private final ErasureCodingPolicy ecPolicy = - ErasureCodingPolicyManager.getSystemDefaultPolicy(); + StripedFileTestUtil.getDefaultECPolicy(); private int dataBlocks = ecPolicy.getNumDataUnits(); private int parityBlocks = ecPolicy.getNumParityUnits(); @@ -82,7 +81,7 @@ public class TestFileChecksum { Path ecPath = new Path(ecDir); cluster.getFileSystem().mkdir(ecPath, FsPermission.getDirDefault()); cluster.getFileSystem().getClient().setErasureCodingPolicy(ecDir, - ErasureCodingPolicyManager.getSystemDefaultPolicy().getName()); + StripedFileTestUtil.getDefaultECPolicy().getName()); fs = cluster.getFileSystem(); client = fs.getClient(); diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestFileStatusWithECPolicy.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestFileStatusWithECPolicy.java index d7b73274a51..3be0e8d585a 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestFileStatusWithECPolicy.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestFileStatusWithECPolicy.java @@ -26,7 +26,6 @@ import java.io.IOException; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; import org.apache.hadoop.fs.permission.FsPermission; -import org.apache.hadoop.hdfs.server.namenode.ErasureCodingPolicyManager; import org.apache.hadoop.hdfs.protocol.ErasureCodingPolicy; import org.junit.After; import org.junit.Before; @@ -71,7 +70,7 @@ public class TestFileStatusWithECPolicy { assertNull(client.getFileInfo(file.toString()).getErasureCodingPolicy()); fs.delete(file, true); - final ErasureCodingPolicy ecPolicy1 = ErasureCodingPolicyManager.getSystemDefaultPolicy(); + final ErasureCodingPolicy ecPolicy1 = StripedFileTestUtil.getDefaultECPolicy(); // set EC policy on dir fs.setErasureCodingPolicy(dir, ecPolicy1.getName()); final ErasureCodingPolicy ecPolicy2 = client.getFileInfo(dir.toUri().getPath()).getErasureCodingPolicy(); diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestLeaseRecoveryStriped.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestLeaseRecoveryStriped.java index 710ff622b40..2ba5aede855 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestLeaseRecoveryStriped.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestLeaseRecoveryStriped.java @@ -29,7 +29,6 @@ import org.apache.hadoop.hdfs.client.HdfsClientConfigKeys; import org.apache.hadoop.hdfs.protocol.DatanodeInfo; import org.apache.hadoop.hdfs.protocol.ErasureCodingPolicy; import org.apache.hadoop.hdfs.server.datanode.DataNode; -import org.apache.hadoop.hdfs.server.namenode.ErasureCodingPolicyManager; import org.apache.hadoop.hdfs.util.StripedBlockUtil; import org.apache.hadoop.io.IOUtils; import org.apache.hadoop.security.UserGroupInformation; @@ -57,7 +56,7 @@ public class TestLeaseRecoveryStriped { .getLog(TestLeaseRecoveryStriped.class); private final ErasureCodingPolicy ecPolicy = - ErasureCodingPolicyManager.getSystemDefaultPolicy(); + StripedFileTestUtil.getDefaultECPolicy(); private final int dataBlocks = ecPolicy.getNumDataUnits(); private final int parityBlocks = ecPolicy.getNumParityUnits(); private final int cellSize = ecPolicy.getCellSize(); diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestReadStripedFileWithDecoding.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestReadStripedFileWithDecoding.java index d7d9cc1c20c..4c6ad99c885 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestReadStripedFileWithDecoding.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestReadStripedFileWithDecoding.java @@ -35,7 +35,6 @@ import org.apache.hadoop.hdfs.server.blockmanagement.BlockPlacementPolicy; import org.apache.hadoop.hdfs.server.blockmanagement.DatanodeDescriptor; import org.apache.hadoop.hdfs.server.datanode.DataNode; import org.apache.hadoop.hdfs.server.datanode.DataNodeTestUtils; -import org.apache.hadoop.hdfs.server.namenode.ErasureCodingPolicyManager; import org.apache.hadoop.hdfs.server.namenode.FSNamesystem; import org.apache.hadoop.hdfs.server.namenode.NameNode; import org.apache.hadoop.hdfs.server.namenode.NameNodeAdapter; @@ -68,7 +67,7 @@ public class TestReadStripedFileWithDecoding { private MiniDFSCluster cluster; private DistributedFileSystem fs; private final ErasureCodingPolicy ecPolicy = - ErasureCodingPolicyManager.getSystemDefaultPolicy(); + StripedFileTestUtil.getDefaultECPolicy(); private final short dataBlocks = (short) ecPolicy.getNumDataUnits(); private final short parityBlocks = (short) ecPolicy.getNumParityUnits(); @@ -103,7 +102,7 @@ public class TestReadStripedFileWithDecoding { false); cluster = new MiniDFSCluster.Builder(conf).numDataNodes(numDNs).build(); cluster.getFileSystem().getClient().setErasureCodingPolicy("/", - ErasureCodingPolicyManager.getSystemDefaultPolicy().getName()); + StripedFileTestUtil.getDefaultECPolicy().getName()); fs = cluster.getFileSystem(); } diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestReadStripedFileWithMissingBlocks.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestReadStripedFileWithMissingBlocks.java index b65626e149c..930986f0764 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestReadStripedFileWithMissingBlocks.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestReadStripedFileWithMissingBlocks.java @@ -24,7 +24,6 @@ import org.apache.hadoop.fs.BlockLocation; import org.apache.hadoop.fs.Path; import org.apache.hadoop.hdfs.protocol.ErasureCodingPolicy; import org.apache.hadoop.hdfs.server.datanode.DataNode; -import org.apache.hadoop.hdfs.server.namenode.ErasureCodingPolicyManager; import org.junit.Assert; import org.junit.Test; import org.junit.Rule; @@ -43,7 +42,7 @@ public class TestReadStripedFileWithMissingBlocks { private DistributedFileSystem fs; private Configuration conf = new HdfsConfiguration(); private final ErasureCodingPolicy ecPolicy = - ErasureCodingPolicyManager.getSystemDefaultPolicy(); + StripedFileTestUtil.getDefaultECPolicy(); private final short dataBlocks = (short) ecPolicy.getNumDataUnits(); private final short parityBlocks = (short) ecPolicy.getNumParityUnits(); private final int cellSize = ecPolicy.getCellSize(); diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestReconstructStripedFile.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestReconstructStripedFile.java index 38939f5482e..88edcb40d82 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestReconstructStripedFile.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestReconstructStripedFile.java @@ -44,7 +44,6 @@ import org.apache.hadoop.hdfs.server.blockmanagement.BlockManager; import org.apache.hadoop.hdfs.server.blockmanagement.BlockManagerTestUtil; import org.apache.hadoop.hdfs.server.blockmanagement.DatanodeStorageInfo; import org.apache.hadoop.hdfs.server.datanode.DataNode; -import org.apache.hadoop.hdfs.server.namenode.ErasureCodingPolicyManager; import org.apache.hadoop.hdfs.server.protocol.DatanodeStorage; import org.apache.hadoop.hdfs.server.protocol.BlockECReconstructionCommand.BlockECReconstructionInfo; import org.apache.hadoop.hdfs.util.StripedBlockUtil; @@ -62,7 +61,7 @@ public class TestReconstructStripedFile { public static final Log LOG = LogFactory.getLog(TestReconstructStripedFile.class); private final ErasureCodingPolicy ecPolicy = - ErasureCodingPolicyManager.getSystemDefaultPolicy(); + StripedFileTestUtil.getDefaultECPolicy(); private final int dataBlkNum = ecPolicy.getNumDataUnits(); private final int parityBlkNum = ecPolicy.getNumParityUnits(); private final int cellSize = ecPolicy.getCellSize(); @@ -108,7 +107,7 @@ public class TestReconstructStripedFile { fs = cluster.getFileSystem(); fs.getClient().setErasureCodingPolicy("/", - ErasureCodingPolicyManager.getSystemDefaultPolicy().getName()); + StripedFileTestUtil.getDefaultECPolicy().getName()); List datanodes = cluster.getDataNodes(); for (int i = 0; i < dnNum; i++) { @@ -418,7 +417,7 @@ public class TestReconstructStripedFile { BlockECReconstructionInfo invalidECInfo = new BlockECReconstructionInfo( new ExtendedBlock("bp-id", 123456), dataDNs, dnStorageInfo, liveIndices, - ErasureCodingPolicyManager.getSystemDefaultPolicy()); + StripedFileTestUtil.getDefaultECPolicy()); List ecTasks = new ArrayList<>(); ecTasks.add(invalidECInfo); dataNode.getErasureCodingWorker().processErasureCodingTasks(ecTasks); diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestSafeModeWithStripedFile.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestSafeModeWithStripedFile.java index 0731779681f..85538ba9161 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestSafeModeWithStripedFile.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestSafeModeWithStripedFile.java @@ -25,7 +25,6 @@ import org.apache.hadoop.fs.Path; import org.apache.hadoop.hdfs.protocol.DatanodeInfo; import org.apache.hadoop.hdfs.protocol.ErasureCodingPolicy; import org.apache.hadoop.hdfs.protocol.LocatedBlocks; -import org.apache.hadoop.hdfs.server.namenode.ErasureCodingPolicyManager; import org.apache.hadoop.hdfs.server.namenode.NameNode; import org.apache.hadoop.hdfs.server.namenode.NameNodeAdapter; import org.junit.After; @@ -44,7 +43,7 @@ import static org.junit.Assert.assertTrue; public class TestSafeModeWithStripedFile { private final ErasureCodingPolicy ecPolicy = - ErasureCodingPolicyManager.getSystemDefaultPolicy(); + StripedFileTestUtil.getDefaultECPolicy(); private final short dataBlocks = (short) ecPolicy.getNumDataUnits(); private final short parityBlocks = (short) ecPolicy.getNumParityUnits(); private final int numDNs = dataBlocks + parityBlocks; @@ -64,7 +63,7 @@ public class TestSafeModeWithStripedFile { conf.setLong(DFSConfigKeys.DFS_BLOCKREPORT_INTERVAL_MSEC_KEY, 100); cluster = new MiniDFSCluster.Builder(conf).numDataNodes(numDNs).build(); cluster.getFileSystem().getClient().setErasureCodingPolicy("/", - ErasureCodingPolicyManager.getSystemDefaultPolicy().getName()); + StripedFileTestUtil.getDefaultECPolicy().getName()); cluster.waitActive(); } diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestUnsetAndChangeDirectoryEcPolicy.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestUnsetAndChangeDirectoryEcPolicy.java index ec19a744d54..32f6bc8013c 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestUnsetAndChangeDirectoryEcPolicy.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestUnsetAndChangeDirectoryEcPolicy.java @@ -51,8 +51,7 @@ public class TestUnsetAndChangeDirectoryEcPolicy { private MiniDFSCluster cluster; private Configuration conf = new Configuration(); private DistributedFileSystem fs; - private ErasureCodingPolicy ecPolicy = ErasureCodingPolicyManager - .getSystemDefaultPolicy(); + private ErasureCodingPolicy ecPolicy = StripedFileTestUtil.getDefaultECPolicy(); private final short dataBlocks = (short) ecPolicy.getNumDataUnits(); private final short parityBlocks = (short) ecPolicy.getNumParityUnits(); private final int cellSize = ecPolicy.getCellSize(); diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestWriteReadStripedFile.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestWriteReadStripedFile.java index 76ca704e934..9aac65b411e 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestWriteReadStripedFile.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestWriteReadStripedFile.java @@ -27,7 +27,6 @@ import org.apache.hadoop.fs.Path; import org.apache.hadoop.hdfs.protocol.ErasureCodingPolicy; import org.apache.hadoop.hdfs.server.blockmanagement.BlockPlacementPolicy; import org.apache.hadoop.hdfs.server.datanode.DataNode; -import org.apache.hadoop.hdfs.server.namenode.ErasureCodingPolicyManager; import org.apache.hadoop.hdfs.web.WebHdfsConstants; import org.apache.hadoop.hdfs.web.WebHdfsTestUtil; import org.apache.hadoop.ipc.RemoteException; @@ -48,7 +47,7 @@ import java.util.Random; public class TestWriteReadStripedFile { public static final Log LOG = LogFactory.getLog(TestWriteReadStripedFile.class); private final ErasureCodingPolicy ecPolicy = - ErasureCodingPolicyManager.getSystemDefaultPolicy(); + StripedFileTestUtil.getDefaultECPolicy(); private final int cellSize = ecPolicy.getCellSize(); private final short dataBlocks = (short) ecPolicy.getNumDataUnits(); private final short parityBlocks = (short) ecPolicy.getNumParityUnits(); @@ -81,7 +80,7 @@ public class TestWriteReadStripedFile { fs = cluster.getFileSystem(); fs.mkdirs(new Path("/ec")); cluster.getFileSystem().getClient().setErasureCodingPolicy("/ec", - ErasureCodingPolicyManager.getSystemDefaultPolicy().getName()); + StripedFileTestUtil.getDefaultECPolicy().getName()); } @After diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestWriteStripedFileWithFailure.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestWriteStripedFileWithFailure.java index 23a98214900..03e9e10bf13 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestWriteStripedFileWithFailure.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestWriteStripedFileWithFailure.java @@ -24,7 +24,6 @@ import org.apache.hadoop.fs.FSDataOutputStream; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.hdfs.protocol.ErasureCodingPolicy; -import org.apache.hadoop.hdfs.server.namenode.ErasureCodingPolicyManager; import org.apache.hadoop.test.GenericTestUtils; import org.apache.log4j.Level; import org.junit.Assert; @@ -47,7 +46,7 @@ public class TestWriteStripedFileWithFailure { } private final ErasureCodingPolicy ecPolicy = - ErasureCodingPolicyManager.getSystemDefaultPolicy(); + StripedFileTestUtil.getDefaultECPolicy(); private final short dataBlocks = (short) ecPolicy.getNumDataUnits(); private final short parityBlocks = (short) ecPolicy.getNumParityUnits(); private final int numDNs = dataBlocks + parityBlocks; @@ -60,7 +59,7 @@ public class TestWriteStripedFileWithFailure { conf.setLong(DFSConfigKeys.DFS_BLOCK_SIZE_KEY, blockSize); cluster = new MiniDFSCluster.Builder(conf).numDataNodes(numDNs).build(); cluster.getFileSystem().getClient().setErasureCodingPolicy("/", - ErasureCodingPolicyManager.getSystemDefaultPolicy().getName()); + StripedFileTestUtil.getDefaultECPolicy().getName()); fs = cluster.getFileSystem(); } diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/protocolPB/TestPBHelper.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/protocolPB/TestPBHelper.java index ff0852809ad..ff4b8ec8b1c 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/protocolPB/TestPBHelper.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/protocolPB/TestPBHelper.java @@ -36,6 +36,7 @@ import org.apache.hadoop.fs.permission.AclStatus; import org.apache.hadoop.fs.permission.FsAction; import org.apache.hadoop.fs.StorageType; import org.apache.hadoop.hdfs.DFSTestUtil; +import org.apache.hadoop.hdfs.StripedFileTestUtil; import org.apache.hadoop.hdfs.protocol.Block; import org.apache.hadoop.hdfs.protocol.BlockType; import org.apache.hadoop.hdfs.protocol.DatanodeID; @@ -77,7 +78,6 @@ import org.apache.hadoop.hdfs.server.common.HdfsServerConstants.NamenodeRole; import org.apache.hadoop.hdfs.server.common.HdfsServerConstants.NodeType; import org.apache.hadoop.hdfs.server.common.StorageInfo; import org.apache.hadoop.hdfs.server.namenode.CheckpointSignature; -import org.apache.hadoop.hdfs.server.namenode.ErasureCodingPolicyManager; import org.apache.hadoop.hdfs.server.protocol.BlockCommand; import org.apache.hadoop.hdfs.server.protocol.BlockECReconstructionCommand.BlockECReconstructionInfo; import org.apache.hadoop.hdfs.server.protocol.BlockRecoveryCommand; @@ -228,7 +228,7 @@ public class TestPBHelper { datanodeUuids, storageIDs, storageTypes); if (isStriped) { blkLocs = new StripedBlockWithLocations(blkLocs, indices, dataBlkNum, - ErasureCodingPolicyManager.getSystemDefaultPolicy().getCellSize()); + StripedFileTestUtil.getDefaultECPolicy().getCellSize()); } return blkLocs; } @@ -720,7 +720,7 @@ public class TestPBHelper { byte[] liveBlkIndices0 = new byte[2]; BlockECReconstructionInfo blkECRecoveryInfo0 = new BlockECReconstructionInfo( new ExtendedBlock("bp1", 1234), dnInfos0, targetDnInfos0, - liveBlkIndices0, ErasureCodingPolicyManager.getSystemDefaultPolicy()); + liveBlkIndices0, StripedFileTestUtil.getDefaultECPolicy()); DatanodeInfo[] dnInfos1 = new DatanodeInfo[] { DFSTestUtil.getLocalDatanodeInfo(), DFSTestUtil.getLocalDatanodeInfo() }; DatanodeStorageInfo targetDnInfos_2 = BlockManagerTestUtil @@ -734,7 +734,7 @@ public class TestPBHelper { byte[] liveBlkIndices1 = new byte[2]; BlockECReconstructionInfo blkECRecoveryInfo1 = new BlockECReconstructionInfo( new ExtendedBlock("bp2", 3256), dnInfos1, targetDnInfos1, - liveBlkIndices1, ErasureCodingPolicyManager.getSystemDefaultPolicy()); + liveBlkIndices1, StripedFileTestUtil.getDefaultECPolicy()); List blkRecoveryInfosList = new ArrayList(); blkRecoveryInfosList.add(blkECRecoveryInfo0); blkRecoveryInfosList.add(blkECRecoveryInfo1); @@ -823,8 +823,8 @@ public class TestPBHelper { ErasureCodingPolicy ecPolicy2 = blkECRecoveryInfo2.getErasureCodingPolicy(); // Compare ECPolicies same as default ECPolicy as we used system default // ECPolicy used in this test - compareECPolicies(ErasureCodingPolicyManager.getSystemDefaultPolicy(), ecPolicy1); - compareECPolicies(ErasureCodingPolicyManager.getSystemDefaultPolicy(), ecPolicy2); + compareECPolicies(StripedFileTestUtil.getDefaultECPolicy(), ecPolicy1); + compareECPolicies(StripedFileTestUtil.getDefaultECPolicy(), ecPolicy2); } private void compareECPolicies(ErasureCodingPolicy ecPolicy1, ErasureCodingPolicy ecPolicy2) { diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/balancer/TestBalancer.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/balancer/TestBalancer.java index 70aa4e08ca5..d9a8e5b1659 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/balancer/TestBalancer.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/balancer/TestBalancer.java @@ -46,7 +46,6 @@ import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_WEB_AUTHENTICATION_KERBER import static org.apache.hadoop.test.PlatformAssumptions.assumeNotWindows; import org.apache.hadoop.hdfs.protocol.ErasureCodingPolicy; -import org.apache.hadoop.hdfs.server.namenode.ErasureCodingPolicyManager; import org.junit.AfterClass; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -203,7 +202,7 @@ public class TestBalancer { } private final ErasureCodingPolicy ecPolicy = - ErasureCodingPolicyManager.getSystemDefaultPolicy(); + StripedFileTestUtil.getDefaultECPolicy(); private final int dataBlocks = ecPolicy.getNumDataUnits(); private final int parityBlocks = ecPolicy.getNumParityUnits(); private final int groupSize = dataBlocks + parityBlocks; @@ -1941,7 +1940,7 @@ public class TestBalancer { client = NameNodeProxies.createProxy(conf, cluster.getFileSystem(0).getUri(), ClientProtocol.class).getProxy(); client.setErasureCodingPolicy("/", - ErasureCodingPolicyManager.getSystemDefaultPolicy().getName()); + StripedFileTestUtil.getDefaultECPolicy().getName()); long totalCapacity = sum(capacities); diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/blockmanagement/TestBlockInfoStriped.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/blockmanagement/TestBlockInfoStriped.java index 6a55d8bf336..1040d218578 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/blockmanagement/TestBlockInfoStriped.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/blockmanagement/TestBlockInfoStriped.java @@ -18,8 +18,8 @@ package org.apache.hadoop.hdfs.server.blockmanagement; import org.apache.hadoop.hdfs.DFSTestUtil; +import org.apache.hadoop.hdfs.StripedFileTestUtil; import org.apache.hadoop.hdfs.protocol.Block; -import org.apache.hadoop.hdfs.server.namenode.ErasureCodingPolicyManager; import org.apache.hadoop.hdfs.protocol.ErasureCodingPolicy; import org.junit.Assert; import org.junit.Rule; @@ -43,7 +43,7 @@ public class TestBlockInfoStriped { private static final long BASE_ID = -1600; private final Block baseBlock = new Block(BASE_ID); private final ErasureCodingPolicy testECPolicy - = ErasureCodingPolicyManager.getSystemDefaultPolicy(); + = StripedFileTestUtil.getDefaultECPolicy(); private final int totalBlocks = testECPolicy.getNumDataUnits() + testECPolicy.getNumParityUnits(); private final BlockInfoStriped info = new BlockInfoStriped(baseBlock, diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/blockmanagement/TestBlockTokenWithDFSStriped.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/blockmanagement/TestBlockTokenWithDFSStriped.java index 20c3accbd3e..82c9fde1d5a 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/blockmanagement/TestBlockTokenWithDFSStriped.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/blockmanagement/TestBlockTokenWithDFSStriped.java @@ -20,11 +20,11 @@ package org.apache.hadoop.hdfs.server.blockmanagement; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hdfs.DFSConfigKeys; import org.apache.hadoop.hdfs.MiniDFSCluster; +import org.apache.hadoop.hdfs.StripedFileTestUtil; import org.apache.hadoop.hdfs.protocol.ErasureCodingPolicy; import org.apache.hadoop.hdfs.protocol.LocatedBlock; import org.apache.hadoop.hdfs.protocol.LocatedStripedBlock; import org.apache.hadoop.hdfs.server.balancer.TestBalancer; -import org.apache.hadoop.hdfs.server.namenode.ErasureCodingPolicyManager; import org.apache.hadoop.hdfs.util.StripedBlockUtil; import org.apache.hadoop.net.ServerSocketUtil; import org.junit.Rule; @@ -35,7 +35,7 @@ import java.io.IOException; public class TestBlockTokenWithDFSStriped extends TestBlockTokenWithDFS { private final ErasureCodingPolicy ecPolicy = - ErasureCodingPolicyManager.getSystemDefaultPolicy(); + StripedFileTestUtil.getDefaultECPolicy(); private final int dataBlocks = ecPolicy.getNumDataUnits(); private final int parityBlocks = ecPolicy.getNumParityUnits(); private final int cellSize = ecPolicy.getCellSize(); @@ -84,7 +84,7 @@ public class TestBlockTokenWithDFSStriped extends TestBlockTokenWithDFS { .numDataNodes(numDNs) .build(); cluster.getFileSystem().getClient().setErasureCodingPolicy("/", - ErasureCodingPolicyManager.getSystemDefaultPolicy().getName()); + StripedFileTestUtil.getDefaultECPolicy().getName()); try { cluster.waitActive(); doTestRead(conf, cluster, true); diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/blockmanagement/TestLowRedundancyBlockQueues.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/blockmanagement/TestLowRedundancyBlockQueues.java index 2eb7abff88c..d853762a6e9 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/blockmanagement/TestLowRedundancyBlockQueues.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/blockmanagement/TestLowRedundancyBlockQueues.java @@ -20,8 +20,8 @@ package org.apache.hadoop.hdfs.server.blockmanagement; import java.util.Iterator; +import org.apache.hadoop.hdfs.StripedFileTestUtil; import org.apache.hadoop.hdfs.protocol.Block; -import org.apache.hadoop.hdfs.server.namenode.ErasureCodingPolicyManager; import org.apache.hadoop.hdfs.protocol.ErasureCodingPolicy; import org.junit.Test; @@ -33,7 +33,7 @@ import static org.junit.Assert.fail; public class TestLowRedundancyBlockQueues { private final ErasureCodingPolicy ecPolicy = - ErasureCodingPolicyManager.getSystemDefaultPolicy(); + StripedFileTestUtil.getDefaultECPolicy(); private BlockInfo genBlockInfo(long id) { return new BlockInfoContiguous(new Block(id), (short) 3); diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/blockmanagement/TestReconstructStripedBlocksWithRackAwareness.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/blockmanagement/TestReconstructStripedBlocksWithRackAwareness.java index 832876e4492..9b7c7cd81ed 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/blockmanagement/TestReconstructStripedBlocksWithRackAwareness.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/blockmanagement/TestReconstructStripedBlocksWithRackAwareness.java @@ -23,13 +23,13 @@ import org.apache.hadoop.hdfs.DFSTestUtil; import org.apache.hadoop.hdfs.DistributedFileSystem; import org.apache.hadoop.hdfs.HdfsConfiguration; import org.apache.hadoop.hdfs.MiniDFSCluster; +import org.apache.hadoop.hdfs.StripedFileTestUtil; import org.apache.hadoop.hdfs.protocol.DatanodeInfo; import org.apache.hadoop.hdfs.protocol.ErasureCodingPolicy; import org.apache.hadoop.hdfs.protocol.LocatedBlocks; import org.apache.hadoop.hdfs.protocol.LocatedStripedBlock; import org.apache.hadoop.hdfs.server.datanode.DataNode; import org.apache.hadoop.hdfs.server.datanode.DataNodeTestUtils; -import org.apache.hadoop.hdfs.server.namenode.ErasureCodingPolicyManager; import org.apache.hadoop.hdfs.server.namenode.FSNamesystem; import org.apache.hadoop.hdfs.server.namenode.INodeFile; import org.apache.hadoop.net.NetworkTopology; @@ -59,7 +59,7 @@ public class TestReconstructStripedBlocksWithRackAwareness { } private final ErasureCodingPolicy ecPolicy = - ErasureCodingPolicyManager.getSystemDefaultPolicy(); + StripedFileTestUtil.getDefaultECPolicy(); private final int cellSize = ecPolicy.getCellSize(); private final short dataBlocks = (short) ecPolicy.getNumDataUnits(); private final short parityBlocks = (short) ecPolicy.getNumParityUnits(); @@ -151,7 +151,7 @@ public class TestReconstructStripedBlocksWithRackAwareness { cluster.waitActive(); fs = cluster.getFileSystem(); fs.setErasureCodingPolicy(new Path("/"), - ErasureCodingPolicyManager.getSystemDefaultPolicy().getName()); + StripedFileTestUtil.getDefaultECPolicy().getName()); FSNamesystem fsn = cluster.getNamesystem(); BlockManager bm = fsn.getBlockManager(); @@ -222,7 +222,7 @@ public class TestReconstructStripedBlocksWithRackAwareness { cluster.waitActive(); fs = cluster.getFileSystem(); fs.setErasureCodingPolicy(new Path("/"), - ErasureCodingPolicyManager.getSystemDefaultPolicy().getName()); + StripedFileTestUtil.getDefaultECPolicy().getName()); MiniDFSCluster.DataNodeProperties lastHost = stopDataNode( hosts[hosts.length - 1]); @@ -276,7 +276,7 @@ public class TestReconstructStripedBlocksWithRackAwareness { cluster.waitActive(); fs = cluster.getFileSystem(); fs.setErasureCodingPolicy(new Path("/"), - ErasureCodingPolicyManager.getSystemDefaultPolicy().getName()); + StripedFileTestUtil.getDefaultECPolicy().getName()); final BlockManager bm = cluster.getNamesystem().getBlockManager(); final DatanodeManager dm = bm.getDatanodeManager(); diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/blockmanagement/TestSequentialBlockGroupId.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/blockmanagement/TestSequentialBlockGroupId.java index 7920c593075..3c18112b566 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/blockmanagement/TestSequentialBlockGroupId.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/blockmanagement/TestSequentialBlockGroupId.java @@ -37,9 +37,9 @@ 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.StripedFileTestUtil; import org.apache.hadoop.hdfs.protocol.ErasureCodingPolicy; import org.apache.hadoop.hdfs.protocol.LocatedBlock; -import org.apache.hadoop.hdfs.server.namenode.ErasureCodingPolicyManager; import org.apache.hadoop.hdfs.server.namenode.FSNamesystem; import org.apache.hadoop.test.GenericTestUtils; import org.junit.After; @@ -58,7 +58,7 @@ public class TestSequentialBlockGroupId { .getLog("TestSequentialBlockGroupId"); private final ErasureCodingPolicy ecPolicy = - ErasureCodingPolicyManager.getSystemDefaultPolicy(); + StripedFileTestUtil.getDefaultECPolicy(); private final short REPLICATION = 1; private final long SEED = 0; private final int dataBlocks = ecPolicy.getNumDataUnits(); @@ -89,7 +89,7 @@ public class TestSequentialBlockGroupId { .getBlockIdManager().getBlockGroupIdGenerator(); fs.mkdirs(ecDir); cluster.getFileSystem().getClient().setErasureCodingPolicy("/ecDir", - ErasureCodingPolicyManager.getSystemDefaultPolicy().getName()); + StripedFileTestUtil.getDefaultECPolicy().getName()); } @After diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/blockmanagement/TestSortLocatedStripedBlock.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/blockmanagement/TestSortLocatedStripedBlock.java index 4db361740bd..616b4c340dd 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/blockmanagement/TestSortLocatedStripedBlock.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/blockmanagement/TestSortLocatedStripedBlock.java @@ -27,6 +27,7 @@ import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.StorageType; import org.apache.hadoop.hdfs.DFSConfigKeys; import org.apache.hadoop.hdfs.DFSTestUtil; +import org.apache.hadoop.hdfs.StripedFileTestUtil; import org.apache.hadoop.hdfs.protocol.DatanodeInfo; import org.apache.hadoop.hdfs.protocol.DatanodeInfo.AdminStates; import org.apache.hadoop.hdfs.protocol.ErasureCodingPolicy; @@ -34,7 +35,6 @@ import org.apache.hadoop.hdfs.protocol.ExtendedBlock; import org.apache.hadoop.hdfs.protocol.LocatedBlock; import org.apache.hadoop.hdfs.protocol.LocatedStripedBlock; import org.apache.hadoop.hdfs.security.token.block.BlockTokenIdentifier; -import org.apache.hadoop.hdfs.server.namenode.ErasureCodingPolicyManager; import org.apache.hadoop.hdfs.server.namenode.FSNamesystem; import org.apache.hadoop.security.token.Token; import org.apache.hadoop.util.Time; @@ -54,7 +54,7 @@ public class TestSortLocatedStripedBlock { .getLogger(TestSortLocatedStripedBlock.class); private final ErasureCodingPolicy ecPolicy = - ErasureCodingPolicyManager.getSystemDefaultPolicy(); + StripedFileTestUtil.getDefaultECPolicy(); private final int cellSize = ecPolicy.getCellSize(); private final short dataBlocks = (short) ecPolicy.getNumDataUnits(); private final short parityBlocks = (short) ecPolicy.getNumParityUnits(); diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/datanode/TestBlockRecovery.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/datanode/TestBlockRecovery.java index b64f1e29f33..9f0011c8a5e 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/datanode/TestBlockRecovery.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/datanode/TestBlockRecovery.java @@ -65,6 +65,7 @@ import org.apache.hadoop.hdfs.DFSTestUtil; import org.apache.hadoop.hdfs.DistributedFileSystem; import org.apache.hadoop.hdfs.HdfsConfiguration; import org.apache.hadoop.hdfs.MiniDFSCluster; +import org.apache.hadoop.hdfs.StripedFileTestUtil; import org.apache.hadoop.hdfs.server.protocol.SlowPeerReports; import org.apache.hadoop.util.AutoCloseableLock; import org.apache.hadoop.hdfs.protocol.DatanodeID; @@ -137,7 +138,7 @@ public class TestBlockRecovery { public TestName currentTestName = new TestName(); private final int cellSize = - ErasureCodingPolicyManager.getSystemDefaultPolicy().getCellSize(); + StripedFileTestUtil.getDefaultECPolicy().getCellSize(); private final int bytesPerChecksum = 512; private final int[][][] blockLengthsSuite = { {{11 * cellSize, 10 * cellSize, 9 * cellSize, 8 * cellSize, diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/datanode/TestDataNodeErasureCodingMetrics.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/datanode/TestDataNodeErasureCodingMetrics.java index 7036c7a45b7..d36db233f5f 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/datanode/TestDataNodeErasureCodingMetrics.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/datanode/TestDataNodeErasureCodingMetrics.java @@ -33,7 +33,6 @@ import org.apache.hadoop.hdfs.protocol.LocatedStripedBlock; import org.apache.hadoop.hdfs.server.blockmanagement.BlockManager; import org.apache.hadoop.hdfs.server.blockmanagement.BlockManagerTestUtil; import org.apache.hadoop.hdfs.server.blockmanagement.DatanodeDescriptor; -import org.apache.hadoop.hdfs.server.namenode.ErasureCodingPolicyManager; import org.apache.hadoop.hdfs.server.namenode.NameNodeAdapter; import org.apache.hadoop.metrics2.MetricsRecordBuilder; import static org.apache.hadoop.test.MetricsAsserts.getLongCounter; @@ -55,7 +54,7 @@ public class TestDataNodeErasureCodingMetrics { public static final Log LOG = LogFactory. getLog(TestDataNodeErasureCodingMetrics.class); private final ErasureCodingPolicy ecPolicy = - ErasureCodingPolicyManager.getSystemDefaultPolicy(); + StripedFileTestUtil.getDefaultECPolicy(); private final int dataBlocks = ecPolicy.getNumDataUnits(); private final int parityBlocks = ecPolicy.getNumParityUnits(); private final int cellSize = ecPolicy.getCellSize(); @@ -76,7 +75,7 @@ public class TestDataNodeErasureCodingMetrics { cluster = new MiniDFSCluster.Builder(conf).numDataNodes(numDNs).build(); cluster.waitActive(); cluster.getFileSystem().getClient().setErasureCodingPolicy("/", - ErasureCodingPolicyManager.getSystemDefaultPolicy().getName()); + StripedFileTestUtil.getDefaultECPolicy().getName()); fs = cluster.getFileSystem(); } diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/mover/TestMover.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/mover/TestMover.java index a403ff4bb05..0fcb2755955 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/mover/TestMover.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/mover/TestMover.java @@ -78,7 +78,6 @@ import org.apache.hadoop.hdfs.server.balancer.TestBalancer; import org.apache.hadoop.hdfs.server.datanode.DataNode; import org.apache.hadoop.hdfs.server.datanode.DataNodeTestUtils; import org.apache.hadoop.hdfs.server.mover.Mover.MLocation; -import org.apache.hadoop.hdfs.server.namenode.ErasureCodingPolicyManager; import org.apache.hadoop.hdfs.server.namenode.ha.HATestUtil; import org.apache.hadoop.http.HttpConfig; import org.apache.hadoop.minikdc.MiniKdc; @@ -478,7 +477,7 @@ public class TestMover { } private final ErasureCodingPolicy ecPolicy = - ErasureCodingPolicyManager.getSystemDefaultPolicy(); + StripedFileTestUtil.getDefaultECPolicy(); private final int dataBlocks = ecPolicy.getNumDataUnits(); private final int parityBlocks = ecPolicy.getNumParityUnits(); private final int cellSize = ecPolicy.getCellSize(); @@ -538,7 +537,7 @@ public class TestMover { HdfsConstants.HOT_STORAGE_POLICY_NAME); // set an EC policy on "/bar" directory client.setErasureCodingPolicy(barDir, - ErasureCodingPolicyManager.getSystemDefaultPolicy().getName()); + StripedFileTestUtil.getDefaultECPolicy().getName()); // write file to barDir final String fooFile = "/bar/foo"; diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestAddOverReplicatedStripedBlocks.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestAddOverReplicatedStripedBlocks.java index 670efd69552..6a4cd32a582 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestAddOverReplicatedStripedBlocks.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestAddOverReplicatedStripedBlocks.java @@ -56,7 +56,7 @@ public class TestAddOverReplicatedStripedBlocks { private final Path dirPath = new Path("/striped"); private Path filePath = new Path(dirPath, "file"); private final ErasureCodingPolicy ecPolicy = - ErasureCodingPolicyManager.getSystemDefaultPolicy(); + StripedFileTestUtil.getDefaultECPolicy(); private final short dataBlocks = (short) ecPolicy.getNumDataUnits(); private final short parityBlocks = (short) ecPolicy.getNumParityUnits(); private final short groupSize = (short) (dataBlocks + parityBlocks); @@ -82,7 +82,7 @@ public class TestAddOverReplicatedStripedBlocks { fs = cluster.getFileSystem(); fs.mkdirs(dirPath); fs.getClient().setErasureCodingPolicy(dirPath.toString(), - ErasureCodingPolicyManager.getSystemDefaultPolicy().getName()); + StripedFileTestUtil.getDefaultECPolicy().getName()); } @After @@ -192,7 +192,7 @@ public class TestAddOverReplicatedStripedBlocks { long groupId = bg.getBlock().getBlockId(); Block blk = new Block(groupId, blockSize, gs); BlockInfoStriped blockInfo = new BlockInfoStriped(blk, - ErasureCodingPolicyManager.getSystemDefaultPolicy()); + StripedFileTestUtil.getDefaultECPolicy()); for (int i = 0; i < groupSize; i++) { blk.setBlockId(groupId + i); cluster.injectBlocks(i, Arrays.asList(blk), bpid); diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestAddStripedBlockInFBR.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestAddStripedBlockInFBR.java index 87fbcc63c54..a3afa93e3aa 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestAddStripedBlockInFBR.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestAddStripedBlockInFBR.java @@ -24,6 +24,7 @@ import org.apache.hadoop.hdfs.DFSTestUtil; import org.apache.hadoop.hdfs.DistributedFileSystem; import org.apache.hadoop.hdfs.HdfsConfiguration; import org.apache.hadoop.hdfs.MiniDFSCluster; +import org.apache.hadoop.hdfs.StripedFileTestUtil; import org.apache.hadoop.hdfs.protocol.DatanodeID; import org.apache.hadoop.hdfs.protocol.ErasureCodingPolicy; import org.apache.hadoop.hdfs.server.blockmanagement.BlockInfoStriped; @@ -45,7 +46,7 @@ import java.io.IOException; public class TestAddStripedBlockInFBR { private final ErasureCodingPolicy ecPolicy = - ErasureCodingPolicyManager.getSystemDefaultPolicy(); + StripedFileTestUtil.getDefaultECPolicy(); private final int cellSize = ecPolicy.getCellSize(); private final short dataBlocks = (short) ecPolicy.getNumDataUnits(); private final short parityBlocks = (short) ecPolicy.getNumParityUnits(); @@ -88,7 +89,7 @@ public class TestAddStripedBlockInFBR { dfs.mkdirs(ecDir); dfs.mkdirs(repDir); dfs.getClient().setErasureCodingPolicy(ecDir.toString(), - ErasureCodingPolicyManager.getSystemDefaultPolicy().getName()); + StripedFileTestUtil.getDefaultECPolicy().getName()); // create several non-EC files and one EC file final Path[] repFiles = new Path[groupSize]; diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestAddStripedBlocks.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestAddStripedBlocks.java index 2df1aa49855..2eb23b9f829 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestAddStripedBlocks.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestAddStripedBlocks.java @@ -24,6 +24,7 @@ import org.apache.hadoop.hdfs.DFSTestUtil; import org.apache.hadoop.hdfs.DistributedFileSystem; import org.apache.hadoop.hdfs.HdfsConfiguration; import org.apache.hadoop.hdfs.MiniDFSCluster; +import org.apache.hadoop.hdfs.StripedFileTestUtil; import org.apache.hadoop.hdfs.protocol.Block; import org.apache.hadoop.hdfs.protocol.BlockListAsLongs; import org.apache.hadoop.hdfs.protocol.DatanodeID; @@ -67,7 +68,7 @@ import static org.junit.Assert.assertEquals; public class TestAddStripedBlocks { private final ErasureCodingPolicy ecPolicy = - ErasureCodingPolicyManager.getSystemDefaultPolicy(); + StripedFileTestUtil.getDefaultECPolicy(); private final short dataBlocks = (short) ecPolicy.getNumDataUnits(); private final short parityBlocks = (short) ecPolicy.getNumParityUnits(); private final int cellSize = ecPolicy.getCellSize(); @@ -86,8 +87,8 @@ public class TestAddStripedBlocks { .numDataNodes(groupSize).build(); cluster.waitActive(); dfs = cluster.getFileSystem(); - dfs.getClient().setErasureCodingPolicy("/", ErasureCodingPolicyManager - .getSystemDefaultPolicy().getName()); + dfs.getClient().setErasureCodingPolicy("/", + StripedFileTestUtil.getDefaultECPolicy().getName()); } @After diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestEnabledECPolicies.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestEnabledECPolicies.java index dd4ae0b81ae..7f18d18d731 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestEnabledECPolicies.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestEnabledECPolicies.java @@ -19,6 +19,7 @@ package org.apache.hadoop.hdfs.server.namenode; import org.apache.hadoop.hdfs.DFSConfigKeys; import org.apache.hadoop.hdfs.HdfsConfiguration; +import org.apache.hadoop.hdfs.StripedFileTestUtil; import org.apache.hadoop.hdfs.protocol.ErasureCodingPolicy; import org.apache.hadoop.test.GenericTestUtils; import org.junit.Assert; @@ -73,11 +74,11 @@ public class TestEnabledECPolicies { // Test first with an invalid policy expectInvalidPolicy("not-a-policy"); // Test with an invalid policy and a valid policy - expectInvalidPolicy("not-a-policy," + ErasureCodingPolicyManager - .getSystemDefaultPolicy().getName()); + expectInvalidPolicy("not-a-policy," + + StripedFileTestUtil.getDefaultECPolicy().getName()); // Test with a valid and an invalid policy - expectInvalidPolicy(ErasureCodingPolicyManager - .getSystemDefaultPolicy().getName() + ", not-a-policy"); + expectInvalidPolicy( + StripedFileTestUtil.getDefaultECPolicy().getName() + ", not-a-policy"); // Some more invalid values expectInvalidPolicy("not-a-policy, "); expectInvalidPolicy(" ,not-a-policy, "); @@ -85,8 +86,7 @@ public class TestEnabledECPolicies { @Test public void testValid() throws Exception { - String ecPolicyName = ErasureCodingPolicyManager.getSystemDefaultPolicy() - .getName(); + String ecPolicyName = StripedFileTestUtil.getDefaultECPolicy().getName(); expectValidPolicy(ecPolicyName, 1); expectValidPolicy(ecPolicyName + ", ", 1); expectValidPolicy(",", 0); diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestFSEditLogLoader.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestFSEditLogLoader.java index 72d76b77ef6..104727d83a2 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestFSEditLogLoader.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestFSEditLogLoader.java @@ -46,6 +46,7 @@ import org.apache.hadoop.hdfs.DFSTestUtil; import org.apache.hadoop.hdfs.DistributedFileSystem; import org.apache.hadoop.hdfs.HdfsConfiguration; import org.apache.hadoop.hdfs.MiniDFSCluster; +import org.apache.hadoop.hdfs.StripedFileTestUtil; import org.apache.hadoop.hdfs.protocol.Block; import org.apache.hadoop.hdfs.protocol.ErasureCodingPolicy; import org.apache.hadoop.hdfs.server.blockmanagement.BlockInfo; @@ -99,7 +100,7 @@ public class TestFSEditLogLoader { private static final int NUM_DATA_NODES = 0; private final ErasureCodingPolicy testECPolicy - = ErasureCodingPolicyManager.getSystemDefaultPolicy(); + = StripedFileTestUtil.getDefaultECPolicy(); @Test public void testDisplayRecentEditLogOpCodes() throws IOException { diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestFsck.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestFsck.java index 76c5378062e..b94e6108c4e 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestFsck.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestFsck.java @@ -676,7 +676,7 @@ public class TestFsck { setNumFiles(4).build(); conf.setLong(DFSConfigKeys.DFS_BLOCKREPORT_INTERVAL_MSEC_KEY, 10000L); ErasureCodingPolicy ecPolicy = - ErasureCodingPolicyManager.getSystemDefaultPolicy(); + StripedFileTestUtil.getDefaultECPolicy(); final int dataBlocks = ecPolicy.getNumDataUnits(); final int cellSize = ecPolicy.getCellSize(); final int numAllUnits = dataBlocks + ecPolicy.getNumParityUnits(); @@ -1997,10 +1997,9 @@ public class TestFsck { conf.setLong(DFSConfigKeys.DFS_NAMENODE_ACCESSTIME_PRECISION_KEY, precision); conf.setLong(DFSConfigKeys.DFS_BLOCKREPORT_INTERVAL_MSEC_KEY, 10000L); - int dataBlocks = ErasureCodingPolicyManager - .getSystemDefaultPolicy().getNumDataUnits(); - int parityBlocks = ErasureCodingPolicyManager - .getSystemDefaultPolicy().getNumParityUnits(); + int dataBlocks = StripedFileTestUtil.getDefaultECPolicy().getNumDataUnits(); + int parityBlocks = + StripedFileTestUtil.getDefaultECPolicy().getNumParityUnits(); int totalSize = dataBlocks + parityBlocks; cluster = new MiniDFSCluster.Builder(conf).numDataNodes(totalSize).build(); fs = cluster.getFileSystem(); @@ -2288,12 +2287,10 @@ public class TestFsck { @Test (timeout = 300000) public void testFsckCorruptECFile() throws Exception { DistributedFileSystem fs = null; - int dataBlocks = ErasureCodingPolicyManager - .getSystemDefaultPolicy().getNumDataUnits(); - int parityBlocks = ErasureCodingPolicyManager - .getSystemDefaultPolicy().getNumParityUnits(); - int cellSize = ErasureCodingPolicyManager - .getSystemDefaultPolicy().getCellSize(); + int dataBlocks = StripedFileTestUtil.getDefaultECPolicy().getNumDataUnits(); + int parityBlocks = + StripedFileTestUtil.getDefaultECPolicy().getNumParityUnits(); + int cellSize = StripedFileTestUtil.getDefaultECPolicy().getCellSize(); int totalSize = dataBlocks + parityBlocks; cluster = new MiniDFSCluster.Builder(conf) .numDataNodes(totalSize).build(); @@ -2308,7 +2305,7 @@ public class TestFsck { Path ecDirPath = new Path("/striped"); fs.mkdir(ecDirPath, FsPermission.getDirDefault()); fs.getClient().setErasureCodingPolicy(ecDirPath.toString(), - ErasureCodingPolicyManager.getSystemDefaultPolicy().getName()); + StripedFileTestUtil.getDefaultECPolicy().getName()); Path file = new Path(ecDirPath, "corrupted"); final int length = cellSize * dataBlocks; final byte[] bytes = StripedFileTestUtil.generateBytes(length); @@ -2359,12 +2356,10 @@ public class TestFsck { @Test (timeout = 300000) public void testFsckMissingECFile() throws Exception { DistributedFileSystem fs = null; - int dataBlocks = ErasureCodingPolicyManager - .getSystemDefaultPolicy().getNumDataUnits(); - int parityBlocks = ErasureCodingPolicyManager - .getSystemDefaultPolicy().getNumParityUnits(); - int cellSize = ErasureCodingPolicyManager - .getSystemDefaultPolicy().getCellSize(); + int dataBlocks = StripedFileTestUtil.getDefaultECPolicy().getNumDataUnits(); + int parityBlocks = + StripedFileTestUtil.getDefaultECPolicy().getNumParityUnits(); + int cellSize = StripedFileTestUtil.getDefaultECPolicy().getCellSize(); int totalSize = dataBlocks + parityBlocks; cluster = new MiniDFSCluster.Builder(conf) .numDataNodes(totalSize).build(); @@ -2374,7 +2369,7 @@ public class TestFsck { Path ecDirPath = new Path("/striped"); fs.mkdir(ecDirPath, FsPermission.getDirDefault()); fs.getClient().setErasureCodingPolicy(ecDirPath.toString(), - ErasureCodingPolicyManager.getSystemDefaultPolicy().getName()); + StripedFileTestUtil.getDefaultECPolicy().getName()); Path file = new Path(ecDirPath, "missing"); final int length = cellSize * dataBlocks; final byte[] bytes = StripedFileTestUtil.generateBytes(length); diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestNameNodeMXBean.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestNameNodeMXBean.java index d4d47637310..231f75fc904 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestNameNodeMXBean.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestNameNodeMXBean.java @@ -725,12 +725,10 @@ public class TestNameNodeMXBean { DistributedFileSystem fs = null; try { Configuration conf = new HdfsConfiguration(); - int dataBlocks = ErasureCodingPolicyManager - .getSystemDefaultPolicy().getNumDataUnits(); - int parityBlocks = ErasureCodingPolicyManager - .getSystemDefaultPolicy().getNumParityUnits(); - int cellSize = ErasureCodingPolicyManager - .getSystemDefaultPolicy().getCellSize(); + int dataBlocks = StripedFileTestUtil.getDefaultECPolicy().getNumDataUnits(); + int parityBlocks = + StripedFileTestUtil.getDefaultECPolicy().getNumParityUnits(); + int cellSize = StripedFileTestUtil.getDefaultECPolicy().getCellSize(); int totalSize = dataBlocks + parityBlocks; cluster = new MiniDFSCluster.Builder(conf) .numDataNodes(totalSize).build(); @@ -740,7 +738,7 @@ public class TestNameNodeMXBean { Path ecDirPath = new Path("/striped"); fs.mkdir(ecDirPath, FsPermission.getDirDefault()); fs.getClient().setErasureCodingPolicy(ecDirPath.toString(), - ErasureCodingPolicyManager.getSystemDefaultPolicy().getName()); + StripedFileTestUtil.getDefaultECPolicy().getName()); Path file = new Path(ecDirPath, "corrupted"); final int length = cellSize * dataBlocks; final byte[] bytes = StripedFileTestUtil.generateBytes(length); diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestQuotaWithStripedBlocks.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestQuotaWithStripedBlocks.java index 326ddc84c65..1e836dc09bf 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestQuotaWithStripedBlocks.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestQuotaWithStripedBlocks.java @@ -25,6 +25,7 @@ import org.apache.hadoop.hdfs.DFSConfigKeys; import org.apache.hadoop.hdfs.DFSTestUtil; import org.apache.hadoop.hdfs.DistributedFileSystem; import org.apache.hadoop.hdfs.MiniDFSCluster; +import org.apache.hadoop.hdfs.StripedFileTestUtil; import org.apache.hadoop.hdfs.protocol.Block; import org.apache.hadoop.hdfs.protocol.ErasureCodingPolicy; import org.apache.hadoop.hdfs.protocol.ExtendedBlock; @@ -46,7 +47,7 @@ public class TestQuotaWithStripedBlocks { private static final int BLOCK_SIZE = 1024 * 1024; private static final long DISK_QUOTA = BLOCK_SIZE * 10; private final ErasureCodingPolicy ecPolicy = - ErasureCodingPolicyManager.getSystemDefaultPolicy(); + StripedFileTestUtil.getDefaultECPolicy(); private final int dataBlocks = ecPolicy.getNumDataUnits(); private final int parityBlocsk = ecPolicy.getNumParityUnits(); private final int groupSize = dataBlocks + parityBlocsk; diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestReconstructStripedBlocks.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestReconstructStripedBlocks.java index 5e4a9db39dd..01a8e08bb1a 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestReconstructStripedBlocks.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestReconstructStripedBlocks.java @@ -60,7 +60,7 @@ public class TestReconstructStripedBlocks { public static final Logger LOG = LoggerFactory.getLogger( TestReconstructStripedBlocks.class); private final ErasureCodingPolicy ecPolicy = - ErasureCodingPolicyManager.getSystemDefaultPolicy(); + StripedFileTestUtil.getDefaultECPolicy(); private final int cellSize = ecPolicy.getCellSize(); private final short dataBlocks = (short) ecPolicy.getNumDataUnits(); private final short parityBlocks = (short) ecPolicy.getNumParityUnits(); @@ -202,7 +202,7 @@ public class TestReconstructStripedBlocks { DistributedFileSystem fs = cluster.getFileSystem(); BlockManager bm = cluster.getNamesystem().getBlockManager(); fs.getClient().setErasureCodingPolicy("/", - ErasureCodingPolicyManager.getSystemDefaultPolicy().getName()); + StripedFileTestUtil.getDefaultECPolicy().getName()); int fileLen = dataBlocks * blockSize; Path p = new Path("/test2RecoveryTasksForSameBlockGroup"); final byte[] data = new byte[fileLen]; @@ -268,7 +268,7 @@ public class TestReconstructStripedBlocks { try { fs.mkdirs(dirPath); fs.setErasureCodingPolicy(dirPath, - ErasureCodingPolicyManager.getSystemDefaultPolicy().getName()); + StripedFileTestUtil.getDefaultECPolicy().getName()); DFSTestUtil.createFile(fs, filePath, cellSize * dataBlocks * 2, (short) 1, 0L); diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestStripedINodeFile.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestStripedINodeFile.java index ae9793a6be9..f1610b1839e 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestStripedINodeFile.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestStripedINodeFile.java @@ -39,6 +39,7 @@ import org.apache.hadoop.hdfs.DistributedFileSystem; import org.apache.hadoop.hdfs.HdfsConfiguration; import org.apache.hadoop.hdfs.MiniDFSCluster; import org.apache.hadoop.hdfs.NameNodeProxies; +import org.apache.hadoop.hdfs.StripedFileTestUtil; import org.apache.hadoop.hdfs.protocol.Block; import org.apache.hadoop.hdfs.protocol.BlockStoragePolicy; import org.apache.hadoop.hdfs.protocol.ClientProtocol; @@ -321,7 +322,7 @@ public class TestStripedINodeFile { // set erasure coding policy dfs.setErasureCodingPolicy(ecDir, - ErasureCodingPolicyManager.getSystemDefaultPolicy().getName()); + StripedFileTestUtil.getDefaultECPolicy().getName()); DFSTestUtil.createFile(dfs, ecFile, len, (short) 1, 0xFEED); DFSTestUtil.createFile(dfs, contiguousFile, len, (short) 1, 0xFEED); final FSDirectory fsd = fsn.getFSDirectory(); @@ -423,7 +424,7 @@ public class TestStripedINodeFile { client.setStoragePolicy(fooDir, HdfsConstants.ONESSD_STORAGE_POLICY_NAME); // set an EC policy on "/foo" directory client.setErasureCodingPolicy(fooDir, - ErasureCodingPolicyManager.getSystemDefaultPolicy().getName()); + StripedFileTestUtil.getDefaultECPolicy().getName()); // write file to fooDir final String barFile = "/foo/bar"; diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/tools/offlineImageViewer/TestOfflineImageViewerWithStripedBlocks.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/tools/offlineImageViewer/TestOfflineImageViewerWithStripedBlocks.java index e7794d6c9ec..0bfa0543026 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/tools/offlineImageViewer/TestOfflineImageViewerWithStripedBlocks.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/tools/offlineImageViewer/TestOfflineImageViewerWithStripedBlocks.java @@ -31,12 +31,12 @@ import org.apache.hadoop.hdfs.DFSConfigKeys; import org.apache.hadoop.hdfs.DFSTestUtil; import org.apache.hadoop.hdfs.DistributedFileSystem; import org.apache.hadoop.hdfs.MiniDFSCluster; +import org.apache.hadoop.hdfs.StripedFileTestUtil; import org.apache.hadoop.hdfs.protocol.ErasureCodingPolicy; import org.apache.hadoop.hdfs.protocol.HdfsConstants.SafeModeAction; import org.apache.hadoop.hdfs.protocol.SnapshotAccessControlException; import org.apache.hadoop.hdfs.server.blockmanagement.BlockInfo; import org.apache.hadoop.hdfs.server.blockmanagement.BlockInfoStriped; -import org.apache.hadoop.hdfs.server.namenode.ErasureCodingPolicyManager; import org.apache.hadoop.hdfs.server.namenode.FSDirectory; import org.apache.hadoop.hdfs.server.namenode.FSImageTestUtil; import org.apache.hadoop.hdfs.server.namenode.INodeFile; @@ -46,7 +46,7 @@ import org.junit.Test; public class TestOfflineImageViewerWithStripedBlocks { private final ErasureCodingPolicy ecPolicy = - ErasureCodingPolicyManager.getSystemDefaultPolicy(); + StripedFileTestUtil.getDefaultECPolicy(); private int dataBlocks = ecPolicy.getNumDataUnits(); private int parityBlocks = ecPolicy.getNumParityUnits(); @@ -64,7 +64,7 @@ public class TestOfflineImageViewerWithStripedBlocks { cluster = new MiniDFSCluster.Builder(conf).numDataNodes(numDNs).build(); cluster.waitActive(); cluster.getFileSystem().getClient().setErasureCodingPolicy("/", - ErasureCodingPolicyManager.getSystemDefaultPolicy().getName()); + StripedFileTestUtil.getDefaultECPolicy().getName()); fs = cluster.getFileSystem(); Path eczone = new Path("/eczone"); fs.mkdirs(eczone); @@ -144,7 +144,7 @@ public class TestOfflineImageViewerWithStripedBlocks { // Verify space consumed present in BlockInfoStriped FSDirectory fsdir = cluster.getNamesystem().getFSDirectory(); INodeFile fileNode = fsdir.getINode4Write(file.toString()).asFile(); - assertEquals(ErasureCodingPolicyManager.getSystemDefaultPolicy().getId(), + assertEquals(StripedFileTestUtil.getDefaultECPolicy().getId(), fileNode.getErasureCodingPolicyID()); assertTrue("Invalid block size", fileNode.getBlocks().length > 0); long actualFileSize = 0; From 846a0cd678fba743220f28cef844ac9011a3f934 Mon Sep 17 00:00:00 2001 From: Daniel Templeton Date: Thu, 9 Mar 2017 17:51:47 -0800 Subject: [PATCH 045/188] YARN-1047. Expose # of pre-emptions as a queue counter (Contributed by Karthik Kambatla via Daniel Templeton) --- .../resourcemanager/scheduler/QueueMetrics.java | 13 +++++++++++++ .../scheduler/fair/FSAppAttempt.java | 5 +++++ .../scheduler/fair/TestFairSchedulerPreemption.java | 12 +++++++++--- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/QueueMetrics.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/QueueMetrics.java index 4e364f70f52..007d2b3b9fa 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/QueueMetrics.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/QueueMetrics.java @@ -71,6 +71,8 @@ public class QueueMetrics implements MetricsSource { @Metric("Aggregate # of allocated off-switch containers") MutableCounterLong aggregateOffSwitchContainersAllocated; @Metric("Aggregate # of released containers") MutableCounterLong aggregateContainersReleased; + @Metric("Aggregate # of preempted containers") MutableCounterLong + aggregateContainersPreempted; @Metric("Available memory in MB") MutableGaugeLong availableMB; @Metric("Available CPU in virtual cores") MutableGaugeInt availableVCores; @Metric("Pending memory allocation in MB") MutableGaugeLong pendingMB; @@ -476,6 +478,13 @@ public class QueueMetrics implements MetricsSource { } } + public void preemptContainer() { + aggregateContainersPreempted.incr(); + if (parent != null) { + parent.preemptContainer(); + } + } + public void reserveResource(String user, Resource res) { reservedContainers.incr(); reservedMB.incr(res.getMemorySize()); @@ -640,4 +649,8 @@ public class QueueMetrics implements MetricsSource { public long getAggegatedReleasedContainers() { return aggregateContainersReleased.value(); } + + public long getAggregatePreemptedContainers() { + return aggregateContainersPreempted.value(); + } } diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/fair/FSAppAttempt.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/fair/FSAppAttempt.java index 6c61b451d6d..3a9c94e6568 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/fair/FSAppAttempt.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/fair/FSAppAttempt.java @@ -45,6 +45,7 @@ import org.apache.hadoop.yarn.server.resourcemanager.scheduler.Queue; import org.apache.hadoop.yarn.server.resourcemanager.scheduler.QueueMetrics; import org.apache.hadoop.yarn.server.resourcemanager.scheduler.SchedulerApplicationAttempt; import org.apache.hadoop.yarn.server.resourcemanager.scheduler.SchedulerNode; +import org.apache.hadoop.yarn.server.resourcemanager.scheduler.SchedulerUtils; import org.apache.hadoop.yarn.server.resourcemanager.scheduler.common.PendingAsk; import org.apache.hadoop.yarn.server.scheduler.SchedulerRequestKey; import org.apache.hadoop.yarn.server.utils.BuilderUtils; @@ -161,6 +162,10 @@ public class FSAppAttempt extends SchedulerApplicationAttempt } untrackContainerForPreemption(rmContainer); + if (containerStatus.getDiagnostics(). + equals(SchedulerUtils.PREEMPTED_CONTAINER)) { + queue.getMetrics().preemptContainer(); + } Resource containerResource = rmContainer.getContainer().getResource(); RMAuditLogger.logSuccess(getUser(), AuditConstants.RELEASE_CONTAINER, diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/fair/TestFairSchedulerPreemption.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/fair/TestFairSchedulerPreemption.java index 322ad5b3f58..3940a47aded 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/fair/TestFairSchedulerPreemption.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/fair/TestFairSchedulerPreemption.java @@ -284,14 +284,20 @@ public class TestFairSchedulerPreemption extends FairSchedulerTestBase { Thread.sleep(10); } - // Verify the right amount of containers are preempted from greedyApp - assertEquals("Incorrect number of containers on the greedy app", + // Post preemption, verify the greedyApp has the correct # of containers. + assertEquals("Incorrect # of containers on the greedy app", 2 * numStarvedAppContainers, greedyApp.getLiveContainers().size()); + // Verify the queue metrics are set appropriately. The greedyApp started + // with 8 1GB, 1vcore containers. + assertEquals("Incorrect # of preempted containers in QueueMetrics", + 8 - 2 * numStarvedAppContainers, + greedyApp.getQueue().getMetrics().getAggregatePreemptedContainers()); + sendEnoughNodeUpdatesToAssignFully(); // Verify the preempted containers are assigned to starvingApp - assertEquals("Starved app is not assigned the right number of containers", + assertEquals("Starved app is not assigned the right # of containers", numStarvedAppContainers, starvingApp.getLiveContainers().size()); } From c5ee7fded46dcb1ac1ea4c1ada4949c50bc89afb Mon Sep 17 00:00:00 2001 From: John Zhuge Date: Sun, 5 Mar 2017 22:34:22 -0800 Subject: [PATCH 046/188] HADOOP-14123. Remove misplaced ADL service provider config file for FileSystem. Contributed by John Zhuge. Change-Id: Ic956e2eb8189625916442eaffdc69163d32f730e --- .../META-INF/org.apache.hadoop.fs.FileSystem | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 hadoop-tools/hadoop-azure-datalake/src/main/resources/META-INF/org.apache.hadoop.fs.FileSystem diff --git a/hadoop-tools/hadoop-azure-datalake/src/main/resources/META-INF/org.apache.hadoop.fs.FileSystem b/hadoop-tools/hadoop-azure-datalake/src/main/resources/META-INF/org.apache.hadoop.fs.FileSystem deleted file mode 100644 index 7ec7812295d..00000000000 --- a/hadoop-tools/hadoop-azure-datalake/src/main/resources/META-INF/org.apache.hadoop.fs.FileSystem +++ /dev/null @@ -1,16 +0,0 @@ -# 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. - -org.apache.hadoop.fs.adl.AdlFileSystem \ No newline at end of file From a96afae125ba02fb4480542d3fb0891623ee4c37 Mon Sep 17 00:00:00 2001 From: Karthik Kambatla Date: Thu, 9 Mar 2017 23:11:54 -0800 Subject: [PATCH 047/188] YARN-6264. AM not launched when a single vcore is available on the cluster. (Yufei Gu via kasha) --- .../hadoop/yarn/util/resource/Resources.java | 7 +++++ .../yarn/util/resource/TestResources.java | 24 +++++++++++++++- .../scheduler/fair/FSLeafQueue.java | 3 +- .../scheduler/fair/TestFairScheduler.java | 28 +++++++++---------- 4 files changed, 46 insertions(+), 16 deletions(-) diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/util/resource/Resources.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/util/resource/Resources.java index 57b3a463486..702030030ce 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/util/resource/Resources.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/util/resource/Resources.java @@ -242,6 +242,13 @@ public class Resources { out.setVirtualCores((int)(lhs.getVirtualCores() * by)); return out; } + + public static Resource multiplyAndRoundUp(Resource lhs, double by) { + Resource out = clone(lhs); + out.setMemorySize((long)Math.ceil(lhs.getMemorySize() * by)); + out.setVirtualCores((int)Math.ceil(lhs.getVirtualCores() * by)); + return out; + } public static Resource normalize( ResourceCalculator calculator, Resource lhs, Resource min, diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/test/java/org/apache/hadoop/yarn/util/resource/TestResources.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/test/java/org/apache/hadoop/yarn/util/resource/TestResources.java index 057214badae..f8570a8a2f6 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/test/java/org/apache/hadoop/yarn/util/resource/TestResources.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/test/java/org/apache/hadoop/yarn/util/resource/TestResources.java @@ -20,6 +20,8 @@ package org.apache.hadoop.yarn.util.resource; import org.apache.hadoop.yarn.api.records.Resource; import org.junit.Test; + +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class TestResources { @@ -46,5 +48,25 @@ public class TestResources { assertTrue(Resources.none().compareTo( createResource(0, 1)) < 0); } - + + @Test + public void testMultipleRoundUp() { + final double by = 0.5; + final String memoryErrorMsg = "Invalid memory size."; + final String vcoreErrorMsg = "Invalid virtual core number."; + Resource resource = Resources.createResource(1, 1); + Resource result = Resources.multiplyAndRoundUp(resource, by); + assertEquals(memoryErrorMsg, result.getMemorySize(), 1); + assertEquals(vcoreErrorMsg, result.getVirtualCores(), 1); + + resource = Resources.createResource(2, 2); + result = Resources.multiplyAndRoundUp(resource, by); + assertEquals(memoryErrorMsg, result.getMemorySize(), 1); + assertEquals(vcoreErrorMsg, result.getVirtualCores(), 1); + + resource = Resources.createResource(0, 0); + result = Resources.multiplyAndRoundUp(resource, by); + assertEquals(memoryErrorMsg, result.getMemorySize(), 0); + assertEquals(vcoreErrorMsg, result.getVirtualCores(), 0); + } } diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/fair/FSLeafQueue.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/fair/FSLeafQueue.java index aad291619cf..5be4b361afc 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/fair/FSLeafQueue.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/fair/FSLeafQueue.java @@ -515,7 +515,8 @@ public class FSLeafQueue extends FSQueue { getMaxShare().getVirtualCores())); } - return Resources.multiply(maxResource, maxAMShare); + // Round up to allow AM to run when there is only one vcore on the cluster + return Resources.multiplyAndRoundUp(maxResource, maxAMShare); } /** diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/fair/TestFairScheduler.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/fair/TestFairScheduler.java index 028eea61829..cdf0d81fe2a 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/fair/TestFairScheduler.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/fair/TestFairScheduler.java @@ -659,15 +659,13 @@ public class TestFairScheduler extends FairSchedulerTestBase { // case, we use maxShare, since it is smaller than available resource. assertEquals("QueueFSZeroWithMax's fair share should be zero", 0, queueFSZeroWithMax.getFairShare().getMemorySize()); + Resource expectedAMResource = Resources.multiplyAndRoundUp( + queueFSZeroWithMax.getMaxShare(), queueFSZeroWithMax.getMaxAMShare()); assertEquals("QueueFSZeroWithMax's maximum AM resource should be " - + "maxShare * maxAMShare", - (long)(queueFSZeroWithMax.getMaxShare().getMemorySize() * - queueFSZeroWithMax.getMaxAMShare()), + + "maxShare * maxAMShare", expectedAMResource.getMemorySize(), queueFSZeroWithMax.getMetrics().getMaxAMShareMB()); assertEquals("QueueFSZeroWithMax's maximum AM resource should be " - + "maxShare * maxAMShare", - (long)(queueFSZeroWithMax.getMaxShare().getVirtualCores() * - queueFSZeroWithMax.getMaxAMShare()), + + "maxShare * maxAMShare", expectedAMResource.getVirtualCores(), queueFSZeroWithMax.getMetrics().getMaxAMShareVCores()); assertEquals("QueueFSZeroWithMax's AM resource usage should be the same to " + "AM resource request", @@ -689,17 +687,19 @@ public class TestFairScheduler extends FairSchedulerTestBase { // the min(maxShare, available resource) to compute maxAMShare, in this // case, we use available resource since it is smaller than the // default maxShare. + expectedAMResource = Resources.multiplyAndRoundUp( + Resources.createResource(memCapacity - amResource.getMemorySize(), + cpuCapacity - amResource.getVirtualCores()), + queueFSZeroWithAVL.getMaxAMShare()); assertEquals("QueueFSZeroWithAVL's fair share should be zero", 0, queueFSZeroWithAVL.getFairShare().getMemorySize()); assertEquals("QueueFSZeroWithAVL's maximum AM resource should be " + " available resource * maxAMShare", - (long) ((memCapacity - amResource.getMemorySize()) * - queueFSZeroWithAVL.getMaxAMShare()), + expectedAMResource.getMemorySize(), queueFSZeroWithAVL.getMetrics().getMaxAMShareMB()); assertEquals("QueueFSZeroWithAVL's maximum AM resource should be " + " available resource * maxAMShare", - (long) ((cpuCapacity - amResource.getVirtualCores()) * - queueFSZeroWithAVL.getMaxAMShare()), + expectedAMResource.getVirtualCores(), queueFSZeroWithAVL.getMetrics().getMaxAMShareVCores()); assertEquals("QueueFSZeroWithMax's AM resource usage should be the same to " + "AM resource request", @@ -721,13 +721,13 @@ public class TestFairScheduler extends FairSchedulerTestBase { // fair share to compute maxAMShare assertNotEquals("QueueFSNonZero's fair share shouldn't be zero", 0, queueFSNonZero.getFairShare().getMemorySize()); + expectedAMResource = Resources.multiplyAndRoundUp( + queueFSNonZero.getFairShare(), queueFSNonZero.getMaxAMShare()); assertEquals("QueueFSNonZero's maximum AM resource should be " - + " fair share * maxAMShare", - (long)(memCapacity * queueFSNonZero.getMaxAMShare()), + + " fair share * maxAMShare", expectedAMResource.getMemorySize(), queueFSNonZero.getMetrics().getMaxAMShareMB()); assertEquals("QueueFSNonZero's maximum AM resource should be " - + " fair share * maxAMShare", - (long)(cpuCapacity * queueFSNonZero.getMaxAMShare()), + + " fair share * maxAMShare", expectedAMResource.getVirtualCores(), queueFSNonZero.getMetrics().getMaxAMShareVCores()); assertEquals("QueueFSNonZero's AM resource usage should be the same to " + "AM resource request", From 881ec4d97bd1db4582027aec3a4204156a4eda17 Mon Sep 17 00:00:00 2001 From: Mingliang Liu Date: Tue, 7 Mar 2017 16:29:19 -0800 Subject: [PATCH 048/188] HADOOP-14153. ADL module has messed doc structure. Contributed by Mingliang Liu --- .../src/site/markdown/index.md | 53 +++++++------------ 1 file changed, 20 insertions(+), 33 deletions(-) diff --git a/hadoop-tools/hadoop-azure-datalake/src/site/markdown/index.md b/hadoop-tools/hadoop-azure-datalake/src/site/markdown/index.md index 935524199b3..3a1625375be 100644 --- a/hadoop-tools/hadoop-azure-datalake/src/site/markdown/index.md +++ b/hadoop-tools/hadoop-azure-datalake/src/site/markdown/index.md @@ -14,28 +14,15 @@ # Hadoop Azure Data Lake Support -* [Introduction](#Introduction) -* [Features](#Features) -* [Limitations](#Limitations) -* [Usage](#Usage) - * [Concepts](#Concepts) - * [OAuth2 Support](#OAuth2_Support) - * [Configuring Credentials and FileSystem](#Configuring_Credentials) - * [Using Refresh Token](#Refresh_Token) - * [Using Client Keys](#Client_Credential_Token) - * [Protecting the Credentials with Credential Providers](#Credential_Provider) - * [Enabling ADL Filesystem](#Enabling_ADL) - * [Accessing `adl` URLs](#Accessing_adl_URLs) - * [User/Group Representation](#OIDtoUPNConfiguration) -* [Testing the `hadoop-azure` Module](#Testing_the_hadoop-azure_Module) + -## Introduction +## Introduction The `hadoop-azure-datalake` module provides support for integration with the [Azure Data Lake Store](https://azure.microsoft.com/en-in/documentation/services/data-lake-store/). This support comes via the JAR file `azure-datalake-store.jar`. -## Features +## Features * Read and write data stored in an Azure Data Lake Storage account. * Reference file system paths using URLs using the `adl` scheme for Secure Webhdfs i.e. SSL @@ -46,7 +33,7 @@ This support comes via the JAR file `azure-datalake-store.jar`. * API `setOwner()`, `setAcl`, `removeAclEntries()`, `modifyAclEntries()` accepts UPN or OID (Object ID) as user and group names. -## Limitations +## Limitations Partial or no support for the following operations : @@ -62,9 +49,9 @@ Partial or no support for the following operations : * User and group information returned as `listStatus()` and `getFileStatus()` is in the form of the GUID associated in Azure Active Directory. -## Usage +## Usage -### Concepts +### Concepts Azure Data Lake Storage access path syntax is: ``` @@ -74,7 +61,7 @@ adl://.azuredatalakestore.net/ For details on using the store, see [**Get started with Azure Data Lake Store using the Azure Portal**](https://azure.microsoft.com/en-in/documentation/articles/data-lake-store-get-started-portal/) -### OAuth2 Support +#### OAuth2 Support Usage of Azure Data Lake Storage requires an OAuth2 bearer token to be present as part of the HTTPS header as per the OAuth2 specification. @@ -86,11 +73,11 @@ and identity management service. See [*What is ActiveDirectory*](https://azure.m Following sections describes theOAuth2 configuration in `core-site.xml`. -#### Configuring Credentials & FileSystem +### Configuring Credentials and FileSystem Credentials can be configured using either a refresh token (associated with a user), or a client credential (analogous to a service principal). -#### Using Refresh Tokens +#### Using Refresh Tokens Add the following properties to the cluster's `core-site.xml` @@ -119,9 +106,9 @@ service associated with the client id. See [*Active Directory Library For Java*] ``` -### Using Client Keys +#### Using Client Keys -#### Generating the Service Principal +##### Generating the Service Principal 1. Go to [the portal](https://portal.azure.com) 2. Under "Browse", look for Active Directory and click on it. @@ -135,13 +122,13 @@ service associated with the client id. See [*Active Directory Library For Java*] - The token endpoint (select "View endpoints" at the bottom of the page and copy/paste the OAuth2 .0 Token Endpoint value) - Resource: Always https://management.core.windows.net/ , for all customers -#### Adding the service principal to your ADL Account +##### Adding the service principal to your ADL Account 1. Go to the portal again, and open your ADL account 2. Select Users under Settings 3. Add your user name you created in Step 6 above (note that it does not show up in the list, but will be found if you searched for the name) 4. Add "Owner" role -### Configure core-site.xml +##### Configure core-site.xml Add the following properties to your `core-site.xml` ```xml @@ -161,7 +148,7 @@ Add the following properties to your `core-site.xml` ``` -### Protecting the Credentials with Credential Providers +#### Protecting the Credentials with Credential Providers In many Hadoop clusters, the `core-site.xml` file is world-readable. To protect these credentials, it is recommended that you use the @@ -171,7 +158,7 @@ All ADLS credential properties can be protected by credential providers. For additional reading on the credential provider API, see [Credential Provider API](../hadoop-project-dist/hadoop-common/CredentialProviderAPI.html). -#### Provisioning +##### Provisioning ```bash hadoop credential create dfs.adls.oauth2.refresh.token -value 123 @@ -180,7 +167,7 @@ hadoop credential create dfs.adls.oauth2.credential -value 123 -provider localjceks://file/home/foo/adls.jceks ``` -#### Configuring core-site.xml or command line property +##### Configuring core-site.xml or command line property ```xml @@ -190,7 +177,7 @@ hadoop credential create dfs.adls.oauth2.credential -value 123 ``` -#### Running DistCp +##### Running DistCp ```bash hadoop distcp @@ -203,7 +190,7 @@ NOTE: You may optionally add the provider path property to the `distcp` command line instead of added job specific configuration to a generic `core-site.xml`. The square brackets above illustrate this capability.` -### Accessing adl URLs +### Accessing adl URLs After credentials are configured in `core-site.xml`, any Hadoop component may reference files in that Azure Data Lake Storage account by using URLs of the following @@ -230,7 +217,7 @@ hadoop fs -put testFile adl://yourcontainer.azuredatalakestore.net/testDir/testF hadoop fs -cat adl://yourcontainer.azuredatalakestore.net/testDir/testFile test file content ``` -### User/Group Representation +### User/Group Representation The `hadoop-azure-datalake` module provides support for configuring how User/Group information is represented during @@ -254,7 +241,7 @@ Add the following properties to `core-site.xml` ``` -## Testing the azure-datalake-store Module +## Testing the azure-datalake-store Module The `hadoop-azure` module includes a full suite of unit tests. Most of the tests will run without additional configuration by running mvn test. This includes tests against mocked storage, which is an in-memory emulation of Azure Data Lake Storage. From fd26783aaf3deea7a4e197439bd1075a6689681f Mon Sep 17 00:00:00 2001 From: Mingliang Liu Date: Fri, 10 Mar 2017 00:21:20 -0800 Subject: [PATCH 049/188] HADOOP-13946. Document how HDFS updates timestamps in the FS spec; compare with object stores. Contributed by Steve Loughran --- .../site/markdown/filesystem/introduction.md | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/hadoop-common-project/hadoop-common/src/site/markdown/filesystem/introduction.md b/hadoop-common-project/hadoop-common/src/site/markdown/filesystem/introduction.md index f6db557c4a7..12a796717df 100644 --- a/hadoop-common-project/hadoop-common/src/site/markdown/filesystem/introduction.md +++ b/hadoop-common-project/hadoop-common/src/site/markdown/filesystem/introduction.md @@ -392,3 +392,88 @@ Object stores with these characteristics, can not be used as a direct replacemen for HDFS. In terms of this specification, their implementations of the specified operations do not match those required. They are considered supported by the Hadoop development community, but not to the same extent as HDFS. + +#### Timestamps + + +`FileStatus` entries have a modification time and an access time. + +1. The exact behavior as to when these timestamps are set and whether or not they are valid +varies between filesystems, and potentially between individual installations of a filesystem. +1. The granularity of the timestamps is again, specific to both a filesystem +and potentially individual installations. + +The HDFS filesystem does not update the modification time while it is being written to. + +Specifically + +* `FileSystem.create()` creation: a zero-byte file is listed; the modification time is + set to the current time as seen on the NameNode. +* Writes to a file via the output stream returned in the `create()` call: the modification + time *does not change*. +* When `OutputStream.close()` is called, all remaining data is written, the file closed and + the NameNode updated with the final size of the file. The modification time is set to + the time the file was closed. +* Opening a file for appends via an `append()` operation does not change the modification + time of the file until the `close()` call is made on the output stream. +* `FileSystem.setTimes()` can be used to explicitly set the time on a file. +* When a file is renamed, its modification time is not changed, but the source + and destination directories have their modification times updated. +* The rarely used operations: `FileSystem.concat()`, `createSnapshot()`, + `createSymlink()` and `truncate()` all update the modification time. +* The access time granularity is set in milliseconds `dfs.namenode.access.time.precision`; + the default granularity is 1 hour. If the precision is set to zero, access times + are not recorded. +* If a modification or access time is not set, the value of that `FileStatus` +field is 0. + +Other filesystems may have different behaviors. In particular, + +* Access times may or may not be supported; even if the underlying FS may support access times, + the option it is often disabled for performance reasons. +* The granularity of the timestamps is an implementation-specific detail. + + +Object stores have an even vaguer view of time, which can be summarized as +"it varies". + + * The timestamp granularity is likely to be 1 second, that being the granularity + of timestamps returned in HTTP HEAD and GET requests. + * Access times are likely to be unset. That is, `FileStatus.getAccessTime() == 0`. + * The modification timestamp for a newly created file MAY be that of the + `create()` call, or the actual time which the PUT request was initiated. + This may be in the `FileSystem.create()` call, the final + `OutputStream.close()` operation, some period in between. + * The modification time may not be updated in the `close()` call. + * The timestamp is likely to be in UTC or the TZ of the object store. If the + client is in a different timezone, the timestamp of objects may be ahead or + behind that of the client. + * Object stores with cached metadata databases (for example: AWS S3 with + an in-memory or a DynamoDB metadata store) may have timestamps generated + from the local system clock, rather than that of the service. + This is an optimization to avoid round-trip calls to the object stores. + + A file's modification time is often the same as its creation time. + + The `FileSystem.setTimes()` operation to set file timestamps *may* be ignored. + * `FileSystem.chmod()` may update modification times (example: Azure `wasb://`). + * If `FileSystem.append()` is supported, the changes and modification time + are likely to only become visible after the output stream is closed. + * Out-of-band operations to data in object stores (that is: direct requests + to object stores which bypass the Hadoop FileSystem APIs), may result + in different timestamps being stored and/or returned. + * As the notion of a directory structure is often simulated, the timestamps + of directories *may* be artificially generated —perhaps using the current + system time. + * As `rename()` operations are often implemented as a COPY + DELETE, the + timestamps of renamed objects may become that of the time the rename of an + object was started, rather than the timestamp of the source object. + * The exact timestamp behavior may vary between different object store installations, + even with the same timestore client. + +Finally, note that the Apache Hadoop project cannot make any guarantees about +whether the timestamp behavior of a remote object store will remain consistent +over time: they are third-party services, usually accessed via third-party libraries. + +The best strategy here is "experiment with the exact endpoint you intend to work with". +Furthermore, if you intend to use any caching/consistency layer, test with that +feature enabled. Retest after updates to Hadoop releases, and endpoint object +store updates. From e06ff18ab68d23a0f236df8a0603a42367927f3c Mon Sep 17 00:00:00 2001 From: Sunil G Date: Fri, 10 Mar 2017 16:17:48 +0530 Subject: [PATCH 050/188] YARN-6196. Improve Resource Donut chart with better label in Node page of new YARN UI. Contributed by Akhil PB. --- .../webapp/app/helpers/log-files-comma.js | 10 +++++++- .../app/serializers/yarn-node-container.js | 2 +- .../main/webapp/app/serializers/yarn-node.js | 4 ++-- .../webapp/app/serializers/yarn-rm-node.js | 4 ++-- .../main/webapp/app/templates/yarn-node.hbs | 23 ++++++++++++------- .../main/webapp/app/templates/yarn-nodes.hbs | 2 +- .../webapp/app/templates/yarn-nodes/table.hbs | 13 ++++++----- 7 files changed, 37 insertions(+), 21 deletions(-) diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/helpers/log-files-comma.js b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/helpers/log-files-comma.js index 78dcf257bf5..026cd7f9916 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/helpers/log-files-comma.js +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/helpers/log-files-comma.js @@ -35,8 +35,16 @@ export default Ember.Helper.helper(function(params,hash) { var containerId = hash.containerId; var html = ''; for (var i = 0; i < logFilesLen; i++) { + var logFileName = ""; + if (logFiles[i]) { + if (typeof logFiles[i] === "object" && logFiles[i].containerLogFiles) { + logFileName = logFiles[i].containerLogFiles; + } else if (typeof logFiles[i] === "string") { + logFileName = logFiles[i]; + } + } html = html + '' + logFiles[i] + + nodeAddr + '/' + containerId + '/' + logFileName + '">' + logFileName + ''; if (i !== logFilesLen - 1) { html = html + ","; diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/serializers/yarn-node-container.js b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/serializers/yarn-node-container.js index 7e789879405..7bcb6553c0b 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/serializers/yarn-node-container.js +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/serializers/yarn-node-container.js @@ -30,7 +30,7 @@ export default DS.JSONAPISerializer.extend({ containerId: payload.id, state: payload.state, user: payload.user, - diagnostics: payload.diagnostics, + diagnostics: payload.diagnostics || 'N/A', exitCode: payload.exitCode, totalMemoryNeeded: payload.totalMemoryNeededMB, totalVCoresNeeded: payload.totalVCoresNeeded, diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/serializers/yarn-node.js b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/serializers/yarn-node.js index 0d9faec7231..10521e62a83 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/serializers/yarn-node.js +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/serializers/yarn-node.js @@ -36,8 +36,8 @@ export default DS.JSONAPISerializer.extend({ pmemCheckEnabled: payload.pmemCheckEnabled, nodeHealthy: payload.nodeHealthy, lastNodeUpdateTime: Converter.timeStampToDate(payload.lastNodeUpdateTime), - healthReport: payload.healthReport, - nmStartupTime: Converter.timeStampToDate(payload.nmStartupTime), + healthReport: payload.healthReport || 'N/A', + nmStartupTime: payload.nmStartupTime? Converter.timeStampToDate(payload.nmStartupTime) : '', nodeManagerBuildVersion: payload.nodeManagerBuildVersion, hadoopBuildVersion: payload.hadoopBuildVersion } diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/serializers/yarn-rm-node.js b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/serializers/yarn-rm-node.js index ad5062170b5..1c6d1be859a 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/serializers/yarn-rm-node.js +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/serializers/yarn-rm-node.js @@ -34,7 +34,7 @@ export default DS.JSONAPISerializer.extend({ nodeHostName: payload.nodeHostName, nodeHTTPAddress: payload.nodeHTTPAddress, lastHealthUpdate: Converter.timeStampToDate(payload.lastHealthUpdate), - healthReport: payload.healthReport, + healthReport: payload.healthReport || 'N/A', numContainers: payload.numContainers, usedMemoryMB: payload.usedMemoryMB, availMemoryMB: payload.availMemoryMB, @@ -57,7 +57,7 @@ export default DS.JSONAPISerializer.extend({ normalizeArrayResponse(store, primaryModelClass, payload/*, id, requestType*/) { // expected response is of the form { data: [ {}, {} ] } var normalizedArrayResponse = {}; - if (payload.nodes) { + if (payload.nodes && payload.nodes.node) { // payload is of the form { "nodes": { "node": [ {},{},{} ] } } normalizedArrayResponse.data = payload.nodes.node.map(singleNode => { return this.internalNormalizeSingleResponse(store, primaryModelClass, diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/templates/yarn-node.hbs b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/templates/yarn-node.hbs index b7f727af726..1e8549bd87f 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/templates/yarn-node.hbs +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/templates/yarn-node.hbs @@ -28,7 +28,7 @@
-
Node Information
+
Node Information: {{model.rmNode.id}}
@@ -63,10 +63,12 @@ - - - - + {{#if model.node.nmStartupTime}} + + + + + {{/if}} @@ -82,22 +84,25 @@
-
+
- Resource - Memory (in MB) + Resource - Memory
{{donut-chart data=model.rmNode.getMemoryDataForDonutChart showLabels=true parentId="mem-donut-chart" ratio=0.6 + type="memory" + colorTargets="good" + colorTargetReverse=true maxHeight=350}}
-
+
Resource - VCores @@ -107,6 +112,8 @@ showLabels=true parentId="vcore-donut-chart" ratio=0.6 + colorTargets="good" + colorTargetReverse=true maxHeight=350}}
diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/templates/yarn-nodes.hbs b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/templates/yarn-nodes.hbs index 874b3c46f82..795d00efb11 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/templates/yarn-nodes.hbs +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/templates/yarn-nodes.hbs @@ -29,7 +29,7 @@ + + @return {@link FSDataOutputStream} for created file + + @throws AccessControlException If access is denied + @throws FileAlreadyExistsException If file f already exists + @throws FileNotFoundException If parent of f does not exist + and createParent is false + @throws ParentNotDirectoryException If parent of f is not a + directory. + @throws UnsupportedFileSystemException If file system for f is + not supported + @throws IOException If an I/O error occurred + + Exceptions applicable to file systems accessed over RPC: + @throws RpcClientException If an exception occurred in the RPC client + @throws RpcServerException If an exception occurred in the RPC server + @throws UnexpectedServerException If server implementation throws + undeclared exception to RPC server + + RuntimeExceptions: + @throws InvalidPathException If path f is not valid]]> + + + + + + + + + + + + + + dir already + exists + @throws FileNotFoundException If parent of dir does not exist + and createParent is false + @throws ParentNotDirectoryException If parent of dir is not a + directory + @throws UnsupportedFileSystemException If file system for dir + is not supported + @throws IOException If an I/O error occurred + + Exceptions applicable to file systems accessed over RPC: + @throws RpcClientException If an exception occurred in the RPC client + @throws UnexpectedServerException If server implementation throws + undeclared exception to RPC server + + RuntimeExceptions: + @throws InvalidPathException If path dir is not valid]]> + + + + + + + + + + + f does not exist + @throws UnsupportedFileSystemException If file system for f is + not supported + @throws IOException If an I/O error occurred + + Exceptions applicable to file systems accessed over RPC: + @throws RpcClientException If an exception occurred in the RPC client + @throws RpcServerException If an exception occurred in the RPC server + @throws UnexpectedServerException If server implementation throws + undeclared exception to RPC server + + RuntimeExceptions: + @throws InvalidPathException If path f is invalid]]> + + + + + + + + + + f does not exist + @throws UnsupportedFileSystemException If file system for f + is not supported + @throws IOException If an I/O error occurred + + Exceptions applicable to file systems accessed over RPC: + @throws RpcClientException If an exception occurred in the RPC client + @throws RpcServerException If an exception occurred in the RPC server + @throws UnexpectedServerException If server implementation throws + undeclared exception to RPC server]]> + + + + + + + + + + + f does not exist + @throws UnsupportedFileSystemException If file system for f is + not supported + @throws IOException If an I/O error occurred + + Exceptions applicable to file systems accessed over RPC: + @throws RpcClientException If an exception occurred in the RPC client + @throws RpcServerException If an exception occurred in the RPC server + @throws UnexpectedServerException If server implementation throws + undeclared exception to RPC server]]> + + + + + + + + + + + +
  • Fails if path is a directory. +
  • Fails if path does not exist. +
  • Fails if path is not closed. +
  • Fails if new size is greater than current size. + + @param f The path to the file to be truncated + @param newLength The size the file is to be truncated to + + @return true if the file has been truncated to the desired + newLength and is immediately available to be reused for + write operations such as append, or + false if a background process of adjusting the length of + the last block has been started, and clients should wait for it to + complete before proceeding with further file updates. + + @throws AccessControlException If access is denied + @throws FileNotFoundException If file f does not exist + @throws UnsupportedFileSystemException If file system for f is + not supported + @throws IOException If an I/O error occurred + + Exceptions applicable to file systems accessed over RPC: + @throws RpcClientException If an exception occurred in the RPC client + @throws RpcServerException If an exception occurred in the RPC server + @throws UnexpectedServerException If server implementation throws + undeclared exception to RPC server]]> + + + + + + + + + + f does not exist + @throws IOException If an I/O error occurred + + Exceptions applicable to file systems accessed over RPC: + @throws RpcClientException If an exception occurred in the RPC client + @throws RpcServerException If an exception occurred in the RPC server + @throws UnexpectedServerException If server implementation throws + undeclared exception to RPC server]]> + + + + + + + + + + + + + + +
  • Fails if src is a file and dst is a directory. +
  • Fails if src is a directory and dst is a file. +
  • Fails if the parent of dst does not exist or is a file. + +

    + If OVERWRITE option is not passed as an argument, rename fails if the dst + already exists. +

    + If OVERWRITE option is passed as an argument, rename overwrites the dst if + it is a file or an empty directory. Rename fails if dst is a non-empty + directory. +

    + Note that atomicity of rename is dependent on the file system + implementation. Please refer to the file system documentation for details +

    + + @param src path to be renamed + @param dst new path after rename + + @throws AccessControlException If access is denied + @throws FileAlreadyExistsException If dst already exists and + options has {@link Options.Rename#OVERWRITE} + option false. + @throws FileNotFoundException If src does not exist + @throws ParentNotDirectoryException If parent of dst is not a + directory + @throws UnsupportedFileSystemException If file system for src + and dst is not supported + @throws IOException If an I/O error occurred + + Exceptions applicable to file systems accessed over RPC: + @throws RpcClientException If an exception occurred in the RPC client + @throws RpcServerException If an exception occurred in the RPC server + @throws UnexpectedServerException If server implementation throws + undeclared exception to RPC server]]> + + + + + + + + + + + f does not exist + @throws UnsupportedFileSystemException If file system for f + is not supported + @throws IOException If an I/O error occurred + + Exceptions applicable to file systems accessed over RPC: + @throws RpcClientException If an exception occurred in the RPC client + @throws RpcServerException If an exception occurred in the RPC server + @throws UnexpectedServerException If server implementation throws + undeclared exception to RPC server]]> + + + + + + + + + + + + f does not exist + @throws UnsupportedFileSystemException If file system for f is + not supported + @throws IOException If an I/O error occurred + + Exceptions applicable to file systems accessed over RPC: + @throws RpcClientException If an exception occurred in the RPC client + @throws RpcServerException If an exception occurred in the RPC server + @throws UnexpectedServerException If server implementation throws + undeclared exception to RPC server + + RuntimeExceptions: + @throws HadoopIllegalArgumentException If username or + groupname is invalid.]]> + + + + + + + + + + + + f does not exist + @throws UnsupportedFileSystemException If file system for f is + not supported + @throws IOException If an I/O error occurred + + Exceptions applicable to file systems accessed over RPC: + @throws RpcClientException If an exception occurred in the RPC client + @throws RpcServerException If an exception occurred in the RPC server + @throws UnexpectedServerException If server implementation throws + undeclared exception to RPC server]]> + + + + + + + + + f does not exist + @throws IOException If an I/O error occurred + + Exceptions applicable to file systems accessed over RPC: + @throws RpcClientException If an exception occurred in the RPC client + @throws RpcServerException If an exception occurred in the RPC server + @throws UnexpectedServerException If server implementation throws + undeclared exception to RPC server]]> + + + + + + + + + + + f does not exist + @throws UnsupportedFileSystemException If file system for f is + not supported + @throws IOException If an I/O error occurred + + Exceptions applicable to file systems accessed over RPC: + @throws RpcClientException If an exception occurred in the RPC client + @throws RpcServerException If an exception occurred in the RPC server + @throws UnexpectedServerException If server implementation throws + undeclared exception to RPC server]]> + + + + + + + + + + f does not exist + @throws UnsupportedFileSystemException If file system for f is + not supported + @throws IOException If an I/O error occurred + + Exceptions applicable to file systems accessed over RPC: + @throws RpcClientException If an exception occurred in the RPC client + @throws RpcServerException If an exception occurred in the RPC server + @throws UnexpectedServerException If server implementation throws + undeclared exception to RPC server]]> + + + + + + + + + + f does not exist + @throws UnsupportedFileSystemException If file system for f is + not supported + @throws IOException If an I/O error occurred]]> + + + + + + + + + + f does not exist + @throws UnsupportedFileSystemException If file system for f is + not supported + @throws IOException If the given path does not refer to a symlink + or an I/O error occurred]]> + + + + + + + + + + f does not exist + @throws UnsupportedFileSystemException If file system for f is + not supported + @throws IOException If an I/O error occurred + + Exceptions applicable to file systems accessed over RPC: + @throws RpcClientException If an exception occurred in the RPC client + @throws RpcServerException If an exception occurred in the RPC server + @throws UnexpectedServerException If server implementation throws + undeclared exception to RPC server]]> + + + + + + + + + + + + + + + Given a path referring to a symlink of form: + + <---X---> + fs://host/A/B/link + <-----Y-----> + + In this path X is the scheme and authority that identify the file system, + and Y is the path leading up to the final path component "link". If Y is + a symlink itself then let Y' be the target of Y and X' be the scheme and + authority of Y'. Symlink targets may: + + 1. Fully qualified URIs + + fs://hostX/A/B/file Resolved according to the target file system. + + 2. Partially qualified URIs (eg scheme but no host) + + fs:///A/B/file Resolved according to the target file system. Eg resolving + a symlink to hdfs:///A results in an exception because + HDFS URIs must be fully qualified, while a symlink to + file:///A will not since Hadoop's local file systems + require partially qualified URIs. + + 3. Relative paths + + path Resolves to [Y'][path]. Eg if Y resolves to hdfs://host/A and path + is "../B/file" then [Y'][path] is hdfs://host/B/file + + 4. Absolute paths + + path Resolves to [X'][path]. Eg if Y resolves hdfs://host/A/B and path + is "/file" then [X][path] is hdfs://host/file + + + @param target the target of the symbolic link + @param link the path to be created that points to target + @param createParent if true then missing parent dirs are created if + false then parent must exist + + + @throws AccessControlException If access is denied + @throws FileAlreadyExistsException If file linkcode> already exists + @throws FileNotFoundException If target does not exist + @throws ParentNotDirectoryException If parent of link is not a + directory. + @throws UnsupportedFileSystemException If file system for + target or link is not supported + @throws IOException If an I/O error occurred]]> + + + + + + + + + + f does not exist + @throws UnsupportedFileSystemException If file system for f is + not supported + @throws IOException If an I/O error occurred + + Exceptions applicable to file systems accessed over RPC: + @throws RpcClientException If an exception occurred in the RPC client + @throws RpcServerException If an exception occurred in the RPC server + @throws UnexpectedServerException If server implementation throws + undeclared exception to RPC server]]> + + + + + + + + + + + + + + + + + f does not exist + @throws UnsupportedFileSystemException If file system for f is + not supported + @throws IOException If an I/O error occurred + + Exceptions applicable to file systems accessed over RPC: + @throws RpcClientException If an exception occurred in the RPC client + @throws RpcServerException If an exception occurred in the RPC server + @throws UnexpectedServerException If server implementation throws + undeclared exception to RPC server]]> + + + + + + + + f is + not supported + @throws IOException If an I/O error occurred + + Exceptions applicable to file systems accessed over RPC: + @throws RpcClientException If an exception occurred in the RPC client + @throws RpcServerException If an exception occurred in the RPC server + @throws UnexpectedServerException If server implementation throws + undeclared exception to RPC server]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + describing modifications + @throws IOException if an ACL could not be modified]]> + + + + + + + + describing entries to remove + @throws IOException if an ACL could not be modified]]> + + + + + + + + + + + + + + + + + + + + + + describing modifications, must include entries + for user, group, and others for compatibility with permission bits. + @throws IOException if an ACL could not be modified]]> + + + + + + + which returns each AclStatus + @throws IOException if an ACL could not be read]]> + + + + + + + + + + Refer to the HDFS extended attributes user documentation for details. + + @param path Path to modify + @param name xattr name. + @param value xattr value. + @throws IOException]]> + + + + + + + + + + + Refer to the HDFS extended attributes user documentation for details. + + @param path Path to modify + @param name xattr name. + @param value xattr value. + @param flag xattr set flag + @throws IOException]]> + + + + + + + + + Refer to the HDFS extended attributes user documentation for details. + + @param path Path to get extended attribute + @param name xattr name. + @return byte[] xattr value. + @throws IOException]]> + + + + + + + + Refer to the HDFS extended attributes user documentation for details. + + @param path Path to get extended attributes + @return Map describing the XAttrs of the file or directory + @throws IOException]]> + + + + + + + + + Refer to the HDFS extended attributes user documentation for details. + + @param path Path to get extended attributes + @param names XAttr names. + @return Map describing the XAttrs of the file or directory + @throws IOException]]> + + + + + + + + + Refer to the HDFS extended attributes user documentation for details. + + @param path Path to remove extended attribute + @param name xattr name + @throws IOException]]> + + + + + + + + Refer to the HDFS extended attributes user documentation for details. + + @param path Path to get extended attributes + @return List of the XAttr names of the file or directory + @throws IOException]]> + + + + + + + Exceptions applicable to file systems accessed over RPC: + @throws RpcClientException If an exception occurred in the RPC client + @throws RpcServerException If an exception occurred in the RPC server + @throws UnexpectedServerException If server implementation throws + undeclared exception to RPC server]]> + + + + + + + + Exceptions applicable to file systems accessed over RPC: + @throws RpcClientException If an exception occurred in the RPC client + @throws RpcServerException If an exception occurred in the RPC server + @throws UnexpectedServerException If server implementation throws + undeclared exception to RPC server]]> + + + + + + + + + Exceptions applicable to file systems accessed over RPC: + @throws RpcClientException If an exception occurred in the RPC client + @throws RpcServerException If an exception occurred in the RPC server + @throws UnexpectedServerException If server implementation throws + undeclared exception to RPC server]]> + + + + + + + + Exceptions applicable to file systems accessed over RPC: + @throws RpcClientException If an exception occurred in the RPC client + @throws RpcServerException If an exception occurred in the RPC server + @throws UnexpectedServerException If server implementation throws + undeclared exception to RPC server]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + *** Path Names *** +

    + + The Hadoop file system supports a URI name space and URI names. + It offers a forest of file systems that can be referenced using fully + qualified URIs. + Two common Hadoop file systems implementations are +

      +
    • the local file system: file:///path +
    • the hdfs file system hdfs://nnAddress:nnPort/path +
    + + While URI names are very flexible, it requires knowing the name or address + of the server. For convenience one often wants to access the default system + in one's environment without knowing its name/address. This has an + additional benefit that it allows one to change one's default fs + (e.g. admin moves application from cluster1 to cluster2). +

    + + To facilitate this, Hadoop supports a notion of a default file system. + The user can set his default file system, although this is + typically set up for you in your environment via your default config. + A default file system implies a default scheme and authority; slash-relative + names (such as /for/bar) are resolved relative to that default FS. + Similarly a user can also have working-directory-relative names (i.e. names + not starting with a slash). While the working directory is generally in the + same default FS, the wd can be in a different FS. +

    + Hence Hadoop path names can be one of: +

      +
    • fully qualified URI: scheme://authority/path +
    • slash relative names: /path relative to the default file system +
    • wd-relative names: path relative to the working dir +
    + Relative paths with scheme (scheme:foo/bar) are illegal. + +

    + ****The Role of the FileContext and configuration defaults**** +

    + The FileContext provides file namespace context for resolving file names; + it also contains the umask for permissions, In that sense it is like the + per-process file-related state in Unix system. + These two properties +

      +
    • default file system i.e your slash) +
    • umask +
    + in general, are obtained from the default configuration file + in your environment, (@see {@link Configuration}). + + No other configuration parameters are obtained from the default config as + far as the file context layer is concerned. All file system instances + (i.e. deployments of file systems) have default properties; we call these + server side (SS) defaults. Operation like create allow one to select many + properties: either pass them in as explicit parameters or use + the SS properties. +

    + The file system related SS defaults are +

      +
    • the home directory (default is "/user/userName") +
    • the initial wd (only for local fs) +
    • replication factor +
    • block size +
    • buffer size +
    • encryptDataTransfer +
    • checksum option. (checksumType and bytesPerChecksum) +
    + +

    + *** Usage Model for the FileContext class *** +

    + Example 1: use the default config read from the $HADOOP_CONFIG/core.xml. + Unspecified values come from core-defaults.xml in the release jar. +

      +
    • myFContext = FileContext.getFileContext(); // uses the default config + // which has your default FS +
    • myFContext.create(path, ...); +
    • myFContext.setWorkingDir(path) +
    • myFContext.open (path, ...); +
    + Example 2: Get a FileContext with a specific URI as the default FS +
      +
    • myFContext = FileContext.getFileContext(URI) +
    • myFContext.create(path, ...); + ... +
    + Example 3: FileContext with local file system as the default +
      +
    • myFContext = FileContext.getLocalFSFileContext() +
    • myFContext.create(path, ...); +
    • ... +
    + Example 4: Use a specific config, ignoring $HADOOP_CONFIG + Generally you should not need use a config unless you are doing +
      +
    • configX = someConfigSomeOnePassedToYou. +
    • myFContext = getFileContext(configX); // configX is not changed, + // is passed down +
    • myFContext.create(path, ...); +
    • ... +
    ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This implementation throws an UnsupportedOperationException. + + @return the protocol scheme for the FileSystem.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + fs.scheme.class whose value names the FileSystem class. + The entire URI is passed to the FileSystem instance's initialize method.]]> + + + + + + + + + + + + + + + + + + fs.scheme.class whose value names the FileSystem class. + The entire URI is passed to the FileSystem instance's initialize method. + This always returns a new FileSystem object.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
  • Fails if src is a file and dst is a directory. +
  • Fails if src is a directory and dst is a file. +
  • Fails if the parent of dst does not exist or is a file. + +

    + If OVERWRITE option is not passed as an argument, rename fails + if the dst already exists. +

    + If OVERWRITE option is passed as an argument, rename overwrites + the dst if it is a file or an empty directory. Rename fails if dst is + a non-empty directory. +

    + Note that atomicity of rename is dependent on the file system + implementation. Please refer to the file system documentation for + details. This default implementation is non atomic. +

    + This method is deprecated since it is a temporary method added to + support the transition from FileSystem to FileContext for user + applications. + + @param src path to be renamed + @param dst new path after rename + @throws IOException on failure]]> + + + + + + + + +

  • Fails if path is a directory. +
  • Fails if path does not exist. +
  • Fails if path is not closed. +
  • Fails if new size is greater than current size. + + @param f The path to the file to be truncated + @param newLength The size the file is to be truncated to + + @return true if the file has been truncated to the desired + newLength and is immediately available to be reused for + write operations such as append, or + false if a background process of adjusting the length of + the last block has been started, and clients should wait for it to + complete before proceeding with further file updates.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Does not guarantee to return the List of files/directories status in a + sorted order. + @param f given path + @return the statuses of the files/directories in the given patch + @throws FileNotFoundException when the path does not exist; + IOException see specific implementation]]> + + + + + + + + + + + + + + + + + Does not guarantee to return the List of files/directories status in a + sorted order. + + @param f + a path name + @param filter + the user-supplied path filter + @return an array of FileStatus objects for the files under the given path + after applying the filter + @throws FileNotFoundException when the path does not exist; + IOException see specific implementation]]> + + + + + + + + + Does not guarantee to return the List of files/directories status in a + sorted order. + + @param files + a list of paths + @return a list of statuses for the files under the given paths after + applying the filter default Path filter + @throws FileNotFoundException when the path does not exist; + IOException see specific implementation]]> + + + + + + + + + + Does not guarantee to return the List of files/directories status in a + sorted order. + + @param files + a list of paths + @param filter + the user-supplied path filter + @return a list of statuses for the files under the given paths after + applying the filter + @throws FileNotFoundException when the path does not exist; + IOException see specific implementation]]> + + + + + + + Return all the files that match filePattern and are not checksum + files. Results are sorted by their names. + +

    + A filename pattern is composed of regular characters and + special pattern matching characters, which are: + +

    +
    +
    +

    +

    ? +
    Matches any single character. + +

    +

    * +
    Matches zero or more characters. + +

    +

    [abc] +
    Matches a single character from character set + {a,b,c}. + +

    +

    [a-b] +
    Matches a single character from the character range + {a...b}. Note that character a must be + lexicographically less than or equal to character b. + +

    +

    [^a] +
    Matches a single character that is not from character set or range + {a}. Note that the ^ character must occur + immediately to the right of the opening bracket. + +

    +

    \c +
    Removes (escapes) any special meaning of character c. + +

    +

    {ab,cd} +
    Matches a string from the string set {ab, cd} + +

    +

    {ab,c{de,fh}} +
    Matches a string from the string set {ab, cde, cfh} + +
    +
    +
    + + @param pathPattern a regular expression specifying a pth pattern + + @return an array of paths that match the path pattern + @throws IOException]]> +
    +
    + + + + + + + + + + + + + + f does not exist + @throws IOException If an I/O error occurred]]> + + + + + + + + + f does not exist + @throws IOException if any I/O error occurred]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + describing modifications + @throws IOException if an ACL could not be modified]]> + + + + + + + + describing entries to remove + @throws IOException if an ACL could not be modified]]> + + + + + + + + + + + + + + + + + + + + + + describing modifications, must include entries + for user, group, and others for compatibility with permission bits. + @throws IOException if an ACL could not be modified]]> + + + + + + + + + + + + + + + + + Refer to the HDFS extended attributes user documentation for details. + + @param path Path to modify + @param name xattr name. + @param value xattr value. + @throws IOException]]> + + + + + + + + + + + Refer to the HDFS extended attributes user documentation for details. + + @param path Path to modify + @param name xattr name. + @param value xattr value. + @param flag xattr set flag + @throws IOException]]> + + + + + + + + + Refer to the HDFS extended attributes user documentation for details. + + @param path Path to get extended attribute + @param name xattr name. + @return byte[] xattr value. + @throws IOException]]> + + + + + + + + Refer to the HDFS extended attributes user documentation for details. + + @param path Path to get extended attributes + @return Map describing the XAttrs of the file or directory + @throws IOException]]> + + + + + + + + + Refer to the HDFS extended attributes user documentation for details. + + @param path Path to get extended attributes + @param names XAttr names. + @return Map describing the XAttrs of the file or directory + @throws IOException]]> + + + + + + + + Refer to the HDFS extended attributes user documentation for details. + + @param path Path to get extended attributes + @return List of the XAttr names of the file or directory + @throws IOException]]> + + + + + + + + + Refer to the HDFS extended attributes user documentation for details. + + @param path Path to remove extended attribute + @param name xattr name + @throws IOException]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This is a default method which is intended to be overridden by + subclasses. The default implementation returns an empty storage statistics + object.

    + + @return The StorageStatistics for this FileSystem instance. + Will never be null.]]> +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + All user code that may potentially use the Hadoop Distributed + File System should be written to use a FileSystem object. The + Hadoop DFS is a multi-machine system that appears as a single + disk. It's useful because of its fault tolerance and potentially + very large capacity. + +

    + The local implementation is {@link LocalFileSystem} and distributed + implementation is DistributedFileSystem.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + caller's environment variables to use + for expansion + @return String[] with absolute path to new jar in position 0 and + unexpanded wild card entry path in position 1 + @throws IOException if there is an I/O error while writing the jar file]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + FilterFileSystem contains + some other file system, which it uses as + its basic file system, possibly transforming + the data along the way or providing additional + functionality. The class FilterFileSystem + itself simply overrides all methods of + FileSystem with versions that + pass all requests to the contained file + system. Subclasses of FilterFileSystem + may further override some of these methods + and may also provide additional methods + and fields.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -1 + if there is no more data because the end of the stream has been + reached]]> + + + + + + + + + + length bytes have been read. + + @param position position in the input stream to seek + @param buffer buffer into which data is read + @param offset offset into the buffer in which data is written + @param length the number of bytes to read + @throws IOException IO problems + @throws EOFException If the end of stream is reached while reading. + If an exception is thrown an undetermined number + of bytes in the buffer may have been written.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + path is invalid]]> + + + + + + + + + + + + + + + + + + + + + + + @return file]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + and the scheme is null, and the authority + is null. + + @return whether the path is absolute and the URI has no scheme nor + authority parts]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true if and only if pathname + should be included]]> + + + + + + + + + + + + + + Warning: Not all filesystems satisfy the thread-safety requirement. + @param position position within file + @param buffer destination buffer + @param offset offset in the buffer + @param length number of bytes to read + @return actual number of bytes read; -1 means "none" + @throws IOException IO problems.]]> + + + + + + + + + + Warning: Not all filesystems satisfy the thread-safety requirement. + @param position position within file + @param buffer destination buffer + @param offset offset in the buffer + @param length number of bytes to read + @throws IOException IO problems. + @throws EOFException the end of the data was reached before + the read operation completed]]> + + + + + + + + Warning: Not all filesystems satisfy the thread-safety requirement. + @param position position within file + @param buffer destination buffer + @throws IOException IO problems. + @throws EOFException the end of the data was reached before + the read operation completed]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <----15----> <----15----> <----15----> <-------18-------> + QUOTA REMAINING_QUATA SPACE_QUOTA SPACE_QUOTA_REM FILE_NAME]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + XAttr is byte[], this class is to + covert byte[] to some kind of string representation or convert back. + String representation is convenient for display and input. For example + display in screen as shell response and json response, input as http + or shell parameter.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @return ftp]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A {@link FileSystem} backed by an FTP client provided by Apache Commons Net. +

    ]]> +
    + + + + + + + + + + + + + + + + + + + (cause==null ? null : cause.toString()) (which + typically contains the class and detail message of cause). + @param cause the cause (which is saved for later retrieval by the + {@link #getCause()} method). (A null value is + permitted, and indicates that the cause is nonexistent or + unknown.)]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + But for removeAcl operation it will be false. i.e. AclSpec should + not contain permissions.
    + Example: "user:foo,group:bar" + @return Returns list of {@link AclEntry} parsed]]> +
    +
    + + + + + + The expected format of ACL entries in the string parameter is the same + format produced by the {@link #toStringStable()} method. + + @param aclStr + String representation of an ACL.
    + Example: "user:foo:rw-" + @param includePermission + for setAcl operations this will be true. i.e. Acl should include + permissions.
    + But for removeAcl operation it will be false. i.e. Acl should not + contain permissions.
    + Example: "user:foo,group:bar,mask::" + @return Returns an {@link AclEntry} object]]> +
    +
    + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + unmodifiable ordered list of all ACL entries]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + Recommended to use this API ONLY if client communicates with the old + NameNode, needs to pass the Permission for the path to get effective + permission, else use {@link AclStatus#getEffectivePermission(AclEntry)}. + @param entry AclEntry to get the effective action + @param permArg Permission for the path. However if the client is NOT + communicating with old namenode, then this argument will not have + any preference. + @return Returns the effective permission for the entry. + @throws IllegalArgumentException If the client communicating with old + namenode and permission is not passed as an argument.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + mode is invalid]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @return viewfs]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
  • /user -> hdfs://nnContainingUserDir/user +
  • /project/foo -> hdfs://nnProject1/projects/foo +
  • /project/bar -> hdfs://nnProject2/projects/bar +
  • /tmp -> hdfs://nnTmp/privateTmpForUserXXX + + + ViewFs is specified with the following URI: viewfs:/// +

    + To use viewfs one would typically set the default file system in the + config (i.e. fs.defaultFS < = viewfs:///) along with the + mount table config variables as described below. + +

    + ** Config variables to specify the mount table entries ** +

    + + The file system is initialized from the standard Hadoop config through + config variables. + See {@link FsConstants} for URI and Scheme constants; + See {@link Constants} for config var constants; + see {@link ConfigUtil} for convenient lib. + +

    + All the mount table config entries for view fs are prefixed by + fs.viewfs.mounttable. + For example the above example can be specified with the following + config variables: +

      +
    • fs.viewfs.mounttable.default.link./user= + hdfs://nnContainingUserDir/user +
    • fs.viewfs.mounttable.default.link./project/foo= + hdfs://nnProject1/projects/foo +
    • fs.viewfs.mounttable.default.link./project/bar= + hdfs://nnProject2/projects/bar +
    • fs.viewfs.mounttable.default.link./tmp= + hdfs://nnTmp/privateTmpForUserXXX +
    + + The default mount table (when no authority is specified) is + from config variables prefixed by fs.viewFs.mounttable.default + The authority component of a URI can be used to specify a different mount + table. For example, +
      +
    • viewfs://sanjayMountable/ +
    + is initialized from fs.viewFs.mounttable.sanjayMountable.* config variables. + +

    + **** Merge Mounts **** (NOTE: merge mounts are not implemented yet.) +

    + + One can also use "MergeMounts" to merge several directories (this is + sometimes called union-mounts or junction-mounts in the literature. + For example of the home directories are stored on say two file systems + (because they do not fit on one) then one could specify a mount + entry such as following merges two dirs: +

      +
    • /user -> hdfs://nnUser1/user,hdfs://nnUser2/user +
    + Such a mergeLink can be specified with the following config var where "," + is used as the separator for each of links to be merged: +
      +
    • fs.viewfs.mounttable.default.linkMerge./user= + hdfs://nnUser1/user,hdfs://nnUser1/user +
    + A special case of the merge mount is where mount table's root is merged + with the root (slash) of another file system: +
      +
    • fs.viewfs.mounttable.default.linkMergeSlash=hdfs://nn99/ +
    + In this cases the root of the mount table is merged with the root of + hdfs://nn99/ ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Since these methods are often vendor- or device-specific, operators + may implement this interface in order to achieve fencing. +

    + Fencing is configured by the operator as an ordered list of methods to + attempt. Each method will be tried in turn, and the next in the list + will only be attempted if the previous one fails. See {@link NodeFencer} + for more information. +

    + If an implementation also implements {@link Configurable} then its + setConf method will be called upon instantiation.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + state (e.g ACTIVE/STANDBY) as well as + some additional information. + + @throws AccessControlException + if access is denied. + @throws IOException + if other errors happen + @see HAServiceStatus]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + hadoop.http.filter.initializers. + +

      +
    • StaticUserWebFilter - An authorization plugin that makes all +users a static configured user. +
    ]]> +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + public class IntArrayWritable extends ArrayWritable { + public IntArrayWritable() { + super(IntWritable.class); + } + } + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + o is a ByteWritable with the same value.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + the class of the item + @param conf the configuration to store + @param item the object to be stored + @param keyName the name of the key to use + @throws IOException : forwards Exceptions from the underlying + {@link Serialization} classes.]]> + + + + + + + + + the class of the item + @param conf the configuration to use + @param keyName the name of the key to use + @param itemClass the class of the item + @return restored object + @throws IOException : forwards Exceptions from the underlying + {@link Serialization} classes.]]> + + + + + + + + + the class of the item + @param conf the configuration to use + @param items the objects to be stored + @param keyName the name of the key to use + @throws IndexOutOfBoundsException if the items array is empty + @throws IOException : forwards Exceptions from the underlying + {@link Serialization} classes.]]> + + + + + + + + + the class of the item + @param conf the configuration to use + @param keyName the name of the key to use + @param itemClass the class of the item + @return restored object + @throws IOException : forwards Exceptions from the underlying + {@link Serialization} classes.]]> + + + + + DefaultStringifier offers convenience methods to store/load objects to/from + the configuration. + + @param the class of the objects to stringify]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + o is a DoubleWritable with the same value.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + value argument is null or + its size is zero, the elementType argument must not be null. If + the argument value's size is bigger than zero, the argument + elementType is not be used. + + @param value + @param elementType]]> + + + + + value should not be null + or empty. + + @param value]]> + + + + + + + + + + + + + + value and elementType. If the value argument + is null or its size is zero, the elementType argument must not be + null. If the argument value's size is bigger than zero, the + argument elementType is not be used. + + @param value + @param elementType]]> + + + + + + + + + + + + + + + + + + + o is an EnumSetWritable with the same value, + or both are null.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + o is a FloatWritable with the same value.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + When two sequence files, which have same Key type but different Value + types, are mapped out to reduce, multiple Value types is not allowed. + In this case, this class can help you wrap instances with different types. +

    + +

    + Compared with ObjectWritable, this class is much more effective, + because ObjectWritable will append the class declaration as a String + into the output file in every Key-Value pair. +

    + +

    + Generic Writable implements {@link Configurable} interface, so that it will be + configured by the framework. The configuration is passed to the wrapped objects + implementing {@link Configurable} interface before deserialization. +

    + + how to use it:
    + 1. Write your own class, such as GenericObject, which extends GenericWritable.
    + 2. Implements the abstract method getTypes(), defines + the classes which will be wrapped in GenericObject in application. + Attention: this classes defined in getTypes() method, must + implement Writable interface. +

    + + The code looks like this: +
    + public class GenericObject extends GenericWritable {
    + 
    +   private static Class[] CLASSES = {
    +               ClassType1.class, 
    +               ClassType2.class,
    +               ClassType3.class,
    +               };
    +
    +   protected Class[] getTypes() {
    +       return CLASSES;
    +   }
    +
    + }
    + 
    + + @since Nov 8, 2006]]> +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + o is a IntWritable with the same value.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + closes the input and output streams + at the end. + + @param in InputStrem to read from + @param out OutputStream to write to + @param conf the Configuration object]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ignore any {@link IOException} or + null pointers. Must only be used for cleanup in exception handlers. + + @param log the log to record problems to at debug level. Can be null. + @param closeables the objects to close]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This is better than File#listDir because it does not ignore IOExceptions. + + @param dir The directory to list. + @param filter If non-null, the filter to use when listing + this directory. + @return The list of files in the directory. + + @throws IOException On I/O error]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + o is a LongWritable with the same value.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A map is a directory containing two files, the data file, + containing all keys and values in the map, and a smaller index + file, containing a fraction of the keys. The fraction is determined by + {@link Writer#getIndexInterval()}. + +

    The index file is read entirely into memory. Thus key implementations + should try to keep themselves small. + +

    Map files are created by adding entries in-order. To maintain a large + database, perform updates by copying the previous version of a database and + merging in a sorted change list, to create a new version of the database in + a new file. Sorting large change lists can be done with {@link + SequenceFile.Sorter}.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + o is an MD5Hash whose digest contains the + same values.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + className by first finding + it in the specified conf. If the specified conf is null, + try load it directly.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A {@link Comparator} that operates directly on byte representations of + objects. +

    + @param + @see DeserializerComparator]]> +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SequenceFiles are flat files consisting of binary key/value + pairs. + +

    SequenceFile provides {@link SequenceFile.Writer}, + {@link SequenceFile.Reader} and {@link Sorter} classes for writing, + reading and sorting respectively.

    + + There are three SequenceFile Writers based on the + {@link CompressionType} used to compress key/value pairs: +
      +
    1. + Writer : Uncompressed records. +
    2. +
    3. + RecordCompressWriter : Record-compressed files, only compress + values. +
    4. +
    5. + BlockCompressWriter : Block-compressed files, both keys & + values are collected in 'blocks' + separately and compressed. The size of + the 'block' is configurable. +
    + +

    The actual compression algorithm used to compress key and/or values can be + specified by using the appropriate {@link CompressionCodec}.

    + +

    The recommended way is to use the static createWriter methods + provided by the SequenceFile to chose the preferred format.

    + +

    The {@link SequenceFile.Reader} acts as the bridge and can read any of the + above SequenceFile formats.

    + +

    SequenceFile Formats

    + +

    Essentially there are 3 different formats for SequenceFiles + depending on the CompressionType specified. All of them share a + common header described below. + +

    +
      +
    • + version - 3 bytes of magic header SEQ, followed by 1 byte of actual + version number (e.g. SEQ4 or SEQ6) +
    • +
    • + keyClassName -key class +
    • +
    • + valueClassName - value class +
    • +
    • + compression - A boolean which specifies if compression is turned on for + keys/values in this file. +
    • +
    • + blockCompression - A boolean which specifies if block-compression is + turned on for keys/values in this file. +
    • +
    • + compression codec - CompressionCodec class which is used for + compression of keys and/or values (if compression is + enabled). +
    • +
    • + metadata - {@link Metadata} for this file. +
    • +
    • + sync - A sync marker to denote end of the header. +
    • +
    + +
    Uncompressed SequenceFile Format
    +
      +
    • + Header +
    • +
    • + Record +
        +
      • Record length
      • +
      • Key length
      • +
      • Key
      • +
      • Value
      • +
      +
    • +
    • + A sync-marker every few 100 bytes or so. +
    • +
    + +
    Record-Compressed SequenceFile Format
    +
      +
    • + Header +
    • +
    • + Record +
        +
      • Record length
      • +
      • Key length
      • +
      • Key
      • +
      • Compressed Value
      • +
      +
    • +
    • + A sync-marker every few 100 bytes or so. +
    • +
    + +
    Block-Compressed SequenceFile Format
    +
      +
    • + Header +
    • +
    • + Record Block +
        +
      • Uncompressed number of records in the block
      • +
      • Compressed key-lengths block-size
      • +
      • Compressed key-lengths block
      • +
      • Compressed keys block-size
      • +
      • Compressed keys block
      • +
      • Compressed value-lengths block-size
      • +
      • Compressed value-lengths block
      • +
      • Compressed values block-size
      • +
      • Compressed values block
      • +
      +
    • +
    • + A sync-marker every block. +
    • +
    + +

    The compressed blocks of key lengths and value lengths consist of the + actual lengths of individual keys/values encoded in ZeroCompressedInteger + format.

    + + @see CompressionCodec]]> +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + o is a ShortWritable with the same value.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + the class of the objects to stringify]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + position. Note that this + method avoids using the converter or doing String instantiation + @return the Unicode scalar value at position or -1 + if the position is invalid or points to a + trailing byte]]> + + + + + + + + + + what in the backing + buffer, starting as position start. The starting + position is measured in bytes and the return value is in + terms of byte position in the buffer. The backing buffer is + not converted to a string for this operation. + @return byte position of the first occurence of the search + string in the UTF-8 buffer or -1 if not found]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Note: For performance reasons, this call does not clear the + underlying byte array that is retrievable via {@link #getBytes()}. + In order to free the byte-array memory, call {@link #set(byte[])} + with an empty byte array (For example, new byte[0]).]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + o is a Text with the same contents.]]> + + + + + + + + + + + + + + + + + + + + + + + + + replace is true, then + malformed input is replaced with the + substitution character, which is U+FFFD. Otherwise the + method throws a MalformedInputException.]]> + + + + + + + + + + + + + + + replace is true, then + malformed input is replaced with the + substitution character, which is U+FFFD. Otherwise the + method throws a MalformedInputException. + @return ByteBuffer: bytes stores at ByteBuffer.array() + and length is ByteBuffer.limit()]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + In + addition, it provides methods for string traversal without converting the + byte array to a string.

    Also includes utilities for + serializing/deserialing a string, coding/decoding a string, checking if a + byte array contains valid UTF8 code, calculating the length of an encoded + string.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This is useful when a class may evolve, so that instances written by the + old version of the class may still be processed by the new version. To + handle this situation, {@link #readFields(DataInput)} + implementations should catch {@link VersionMismatchException}.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + o is a VIntWritable with the same value.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + o is a VLongWritable with the same value.]]> + + + + + + + + + + + + + + + + + + + + + + + + out. + + @param out DataOuput to serialize this object into. + @throws IOException]]> + + + + + + + in. + +

    For efficiency, implementations should attempt to re-use storage in the + existing object where possible.

    + + @param in DataInput to deseriablize this object from. + @throws IOException]]> +
    + + + Any key or value type in the Hadoop Map-Reduce + framework implements this interface.

    + +

    Implementations typically implement a static read(DataInput) + method which constructs a new instance, calls {@link #readFields(DataInput)} + and returns the instance.

    + +

    Example:

    +

    +     public class MyWritable implements Writable {
    +       // Some data     
    +       private int counter;
    +       private long timestamp;
    +       
    +       public void write(DataOutput out) throws IOException {
    +         out.writeInt(counter);
    +         out.writeLong(timestamp);
    +       }
    +       
    +       public void readFields(DataInput in) throws IOException {
    +         counter = in.readInt();
    +         timestamp = in.readLong();
    +       }
    +       
    +       public static MyWritable read(DataInput in) throws IOException {
    +         MyWritable w = new MyWritable();
    +         w.readFields(in);
    +         return w;
    +       }
    +     }
    + 

    ]]> +
    + + + + + + + + WritableComparables can be compared to each other, typically + via Comparators. Any type which is to be used as a + key in the Hadoop Map-Reduce framework should implement this + interface.

    + +

    Note that hashCode() is frequently used in Hadoop to partition + keys. It's important that your implementation of hashCode() returns the same + result across different instances of the JVM. Note also that the default + hashCode() implementation in Object does not + satisfy this property.

    + +

    Example:

    +

    +     public class MyWritableComparable implements WritableComparable {
    +       // Some data
    +       private int counter;
    +       private long timestamp;
    +       
    +       public void write(DataOutput out) throws IOException {
    +         out.writeInt(counter);
    +         out.writeLong(timestamp);
    +       }
    +       
    +       public void readFields(DataInput in) throws IOException {
    +         counter = in.readInt();
    +         timestamp = in.readLong();
    +       }
    +       
    +       public int compareTo(MyWritableComparable o) {
    +         int thisValue = this.value;
    +         int thatValue = o.value;
    +         return (thisValue < thatValue ? -1 : (thisValue==thatValue ? 0 : 1));
    +       }
    +
    +       public int hashCode() {
    +         final int prime = 31;
    +         int result = 1;
    +         result = prime * result + counter;
    +         result = prime * result + (int) (timestamp ^ (timestamp >>> 32));
    +         return result
    +       }
    +     }
    + 

    ]]> +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The default implementation reads the data into two {@link + WritableComparable}s (using {@link + Writable#readFields(DataInput)}, then calls {@link + #compare(WritableComparable,WritableComparable)}.]]> + + + + + + + The default implementation uses the natural ordering, calling {@link + Comparable#compareTo(Object)}.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This base implemenation uses the natural ordering. To define alternate + orderings, override {@link #compare(WritableComparable,WritableComparable)}. + +

    One may optimize compare-intensive operations by overriding + {@link #compare(byte[],int,int,byte[],int,int)}. Static utility methods are + provided to assist in optimized implementations of this method.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Enum type + @param in DataInput to read from + @param enumType Class type of Enum + @return Enum represented by String read from DataInput + @throws IOException]]> + + + + + + + + + + + + + + + + len number of bytes in input streamin + @param in input stream + @param len number of bytes to skip + @throws IOException when skipped less number of bytes]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + CompressionCodec for which to get the + Compressor + @param conf the Configuration object which contains confs for creating or reinit the compressor + @return Compressor for the given + CompressionCodec from the pool or a new one]]> + + + + + + + + + CompressionCodec for which to get the + Decompressor + @return Decompressor for the given + CompressionCodec the pool or a new one]]> + + + + + + Compressor to be returned to the pool]]> + + + + + + Decompressor to be returned to the + pool]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Codec aliases are case insensitive. +

    + The code alias is the short class name (without the package name). + If the short class name ends with 'Codec', then there are two aliases for + the codec, the complete short class name and the short class name without + the 'Codec' ending. For example for the 'GzipCodec' codec class name the + alias are 'gzip' and 'gzipcodec'. + + @param codecName the canonical class name of the codec + @return the codec object]]> + + + + + + + Codec aliases are case insensitive. +

    + The code alias is the short class name (without the package name). + If the short class name ends with 'Codec', then there are two aliases for + the codec, the complete short class name and the short class name without + the 'Codec' ending. For example for the 'GzipCodec' codec class name the + alias are 'gzip' and 'gzipcodec'. + + @param codecName the canonical class name of the codec + @return the codec class]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Implementations are assumed to be buffered. This permits clients to + reposition the underlying input stream then call {@link #resetState()}, + without having to also synchronize client buffers.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true indicating that more input data is required. + + @param b Input data + @param off Start offset + @param len Length]]> + + + + + true if the input data buffer is empty and + #setInput() should be called in order to provide more input.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + true if the end of the compressed + data output stream has been reached.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true indicating that more input data is required. + (Both native and non-native versions of various Decompressors require + that the data passed in via b[] remain unmodified until + the caller is explicitly notified--via {@link #needsInput()}--that the + buffer may be safely modified. With this requirement, an extra + buffer-copy can be avoided.) + + @param b Input data + @param off Start offset + @param len Length]]> + + + + + true if the input data buffer is empty and + {@link #setInput(byte[], int, int)} should be called to + provide more input. + + @return true if the input data buffer is empty and + {@link #setInput(byte[], int, int)} should be called in + order to provide more input.]]> + + + + + + + + + + + + + true if a preset dictionary is needed for decompression. + @return true if a preset dictionary is needed for decompression]]> + + + + + true if the end of the decompressed + data output stream has been reached. Indicates a concatenated data stream + when finished() returns true and {@link #getRemaining()} + returns a positive value. finished() will be reset with the + {@link #reset()} method. + @return true if the end of the decompressed + data output stream has been reached.]]> + + + + + + + + + + + + + + true and getRemaining() returns a positive value. If + {@link #finished()} returns true and getRemaining() returns + a zero value, indicates that the end of data stream has been reached and + is not a concatenated data stream. + @return The number of bytes remaining in the compressed data buffer.]]> + + + + + true and {@link #getRemaining()} returns a positive value, + reset() is called before processing of the next data stream in the + concatenated data stream. {@link #finished()} will be reset and will + return false when reset() is called.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

  • "none" - No compression. +
  • "lzo" - LZO compression. +
  • "gz" - GZIP compression. + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
  • Block Compression. +
  • Named meta data blocks. +
  • Sorted or unsorted keys. +
  • Seek by key or by file offset. + + The memory footprint of a TFile includes the following: +
      +
    • Some constant overhead of reading or writing a compressed block. +
        +
      • Each compressed block requires one compression/decompression codec for + I/O. +
      • Temporary space to buffer the key. +
      • Temporary space to buffer the value (for TFile.Writer only). Values are + chunk encoded, so that we buffer at most one chunk of user data. By default, + the chunk buffer is 1MB. Reading chunked value does not require additional + memory. +
      +
    • TFile index, which is proportional to the total number of Data Blocks. + The total amount of memory needed to hold the index can be estimated as + (56+AvgKeySize)*NumBlocks. +
    • MetaBlock index, which is proportional to the total number of Meta + Blocks.The total amount of memory needed to hold the index for Meta Blocks + can be estimated as (40+AvgMetaBlockName)*NumMetaBlock. +
    +

    + The behavior of TFile can be customized by the following variables through + Configuration: +

      +
    • tfile.io.chunk.size: Value chunk size. Integer (in bytes). Default + to 1MB. Values of the length less than the chunk size is guaranteed to have + known value length in read time (See + {@link TFile.Reader.Scanner.Entry#isValueLengthKnown()}). +
    • tfile.fs.output.buffer.size: Buffer size used for + FSDataOutputStream. Integer (in bytes). Default to 256KB. +
    • tfile.fs.input.buffer.size: Buffer size used for + FSDataInputStream. Integer (in bytes). Default to 256KB. +
    +

    + Suggestions on performance optimization. +

      +
    • Minimum block size. We recommend a setting of minimum block size between + 256KB to 1MB for general usage. Larger block size is preferred if files are + primarily for sequential access. However, it would lead to inefficient random + access (because there are more data to decompress). Smaller blocks are good + for random access, but require more memory to hold the block index, and may + be slower to create (because we must flush the compressor stream at the + conclusion of each data block, which leads to an FS I/O flush). Further, due + to the internal caching in Compression codec, the smallest possible block + size would be around 20KB-30KB. +
    • The current implementation does not offer true multi-threading for + reading. The implementation uses FSDataInputStream seek()+read(), which is + shown to be much faster than positioned-read call in single thread mode. + However, it also means that if multiple threads attempt to access the same + TFile (using multiple scanners) simultaneously, the actual I/O is carried out + sequentially even if they access different DFS blocks. +
    • Compression codec. Use "none" if the data is not very compressable (by + compressable, I mean a compression ratio at least 2:1). Generally, use "lzo" + as the starting point for experimenting. "gz" overs slightly better + compression ratio over "lzo" but requires 4x CPU to compress and 2x CPU to + decompress, comparing to "lzo". +
    • File system buffering, if the underlying FSDataInputStream and + FSDataOutputStream is already adequately buffered; or if applications + reads/writes keys and values in large buffers, we can reduce the sizes of + input/output buffering in TFile layer by setting the configuration parameters + "tfile.fs.input.buffer.size" and "tfile.fs.output.buffer.size". +
    + + Some design rationale behind TFile can be found at Hadoop-3315.]]> + + + + + + + + + + + Utils#writeVLong(out, n). + + @param out + output stream + @param n + The integer to be encoded + @throws IOException + @see Utils#writeVLong(DataOutput, long)]]> + + + + + + + + +
  • if n in [-32, 127): encode in one byte with the actual value. + Otherwise, +
  • if n in [-20*2^8, 20*2^8): encode in two bytes: byte[0] = n/256 - 52; + byte[1]=n&0xff. Otherwise, +
  • if n IN [-16*2^16, 16*2^16): encode in three bytes: byte[0]=n/2^16 - + 88; byte[1]=(n>>8)&0xff; byte[2]=n&0xff. Otherwise, +
  • if n in [-8*2^24, 8*2^24): encode in four bytes: byte[0]=n/2^24 - 112; + byte[1] = (n>>16)&0xff; byte[2] = (n>>8)&0xff; byte[3]=n&0xff. Otherwise: +
  • if n in [-2^31, 2^31): encode in five bytes: byte[0]=-125; byte[1] = + (n>>24)&0xff; byte[2]=(n>>16)&0xff; byte[3]=(n>>8)&0xff; byte[4]=n&0xff; +
  • if n in [-2^39, 2^39): encode in six bytes: byte[0]=-124; byte[1] = + (n>>32)&0xff; byte[2]=(n>>24)&0xff; byte[3]=(n>>16)&0xff; + byte[4]=(n>>8)&0xff; byte[5]=n&0xff +
  • if n in [-2^47, 2^47): encode in seven bytes: byte[0]=-123; byte[1] = + (n>>40)&0xff; byte[2]=(n>>32)&0xff; byte[3]=(n>>24)&0xff; + byte[4]=(n>>16)&0xff; byte[5]=(n>>8)&0xff; byte[6]=n&0xff; +
  • if n in [-2^55, 2^55): encode in eight bytes: byte[0]=-122; byte[1] = + (n>>48)&0xff; byte[2] = (n>>40)&0xff; byte[3]=(n>>32)&0xff; + byte[4]=(n>>24)&0xff; byte[5]=(n>>16)&0xff; byte[6]=(n>>8)&0xff; + byte[7]=n&0xff; +
  • if n in [-2^63, 2^63): encode in nine bytes: byte[0]=-121; byte[1] = + (n>>54)&0xff; byte[2] = (n>>48)&0xff; byte[3] = (n>>40)&0xff; + byte[4]=(n>>32)&0xff; byte[5]=(n>>24)&0xff; byte[6]=(n>>16)&0xff; + byte[7]=(n>>8)&0xff; byte[8]=n&0xff; + + + @param out + output stream + @param n + the integer number + @throws IOException]]> + + + + + + + (int)Utils#readVLong(in). + + @param in + input stream + @return the decoded integer + @throws IOException + + @see Utils#readVLong(DataInput)]]> + + + + + + + +
  • if (FB >= -32), return (long)FB; +
  • if (FB in [-72, -33]), return (FB+52)<<8 + NB[0]&0xff; +
  • if (FB in [-104, -73]), return (FB+88)<<16 + (NB[0]&0xff)<<8 + + NB[1]&0xff; +
  • if (FB in [-120, -105]), return (FB+112)<<24 + (NB[0]&0xff)<<16 + + (NB[1]&0xff)<<8 + NB[2]&0xff; +
  • if (FB in [-128, -121]), return interpret NB[FB+129] as a signed + big-endian integer. + + @param in + input stream + @return the decoded long integer. + @throws IOException]]> + + + + + + + + + + + + + + + + + + + + + + + + Type of the input key. + @param list + The list + @param key + The input key. + @param cmp + Comparator for the key. + @return The index to the desired element if it exists; or list.size() + otherwise.]]> + + + + + + + + + Type of the input key. + @param list + The list + @param key + The input key. + @param cmp + Comparator for the key. + @return The index to the desired element if it exists; or list.size() + otherwise.]]> + + + + + + + + Type of the input key. + @param list + The list + @param key + The input key. + @return The index to the desired element if it exists; or list.size() + otherwise.]]> + + + + + + + + Type of the input key. + @param list + The list + @param key + The input key. + @return The index to the desired element if it exists; or list.size() + otherwise.]]> + + + + + + + + + + + + + + + + + An experimental {@link Serialization} for Java {@link Serializable} classes. +

    + @see JavaSerializationComparator]]> +
    +
    + + + + + + + + + A {@link RawComparator} that uses a {@link JavaSerialization} + {@link Deserializer} to deserialize objects that are then compared via + their {@link Comparable} interfaces. +

    + @param + @see JavaSerialization]]> +
    +
    + + + + + + + + + + + + + +This package provides a mechanism for using different serialization frameworks +in Hadoop. The property "io.serializations" defines a list of +{@link org.apache.hadoop.io.serializer.Serialization}s that know how to create +{@link org.apache.hadoop.io.serializer.Serializer}s and +{@link org.apache.hadoop.io.serializer.Deserializer}s. +

    + +

    +To add a new serialization framework write an implementation of +{@link org.apache.hadoop.io.serializer.Serialization} and add its name to the +"io.serializations" property. +

    ]]> +
    +
    + + + + + + + + + + + + + + + + + + + avro.reflect.pkgs or implement + {@link AvroReflectSerializable} interface.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + +This package provides Avro serialization in Hadoop. This can be used to +serialize/deserialize Avro types in Hadoop. +

    + +

    +Use {@link org.apache.hadoop.io.serializer.avro.AvroSpecificSerialization} for +serialization of classes generated by Avro's 'specific' compiler. +

    + +

    +Use {@link org.apache.hadoop.io.serializer.avro.AvroReflectSerialization} for +other classes. +{@link org.apache.hadoop.io.serializer.avro.AvroReflectSerialization} work for +any class which is either in the package list configured via +{@link org.apache.hadoop.io.serializer.avro.AvroReflectSerialization#AVRO_REFLECT_PACKAGES} +or implement {@link org.apache.hadoop.io.serializer.avro.AvroReflectSerializable} +interface. +

    ]]> +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + +The API is abstract so that it can be implemented on top of +a variety of metrics client libraries. The choice of +client library is a configuration option, and different +modules within the same application can use +different metrics implementation libraries. +

    +Sub-packages: +

    +
    org.apache.hadoop.metrics.spi
    +
    The abstract Server Provider Interface package. Those wishing to + integrate the metrics API with a particular metrics client library should + extend this package.
    + +
    org.apache.hadoop.metrics.file
    +
    An implementation package which writes the metric data to + a file, or sends it to the standard output stream.
    + +
    org.apache.hadoop.metrics.ganglia
    +
    An implementation package which sends metric data to + Ganglia.
    +
    + +

    Introduction to the Metrics API

    + +Here is a simple example of how to use this package to report a single +metric value: +
    +    private ContextFactory contextFactory = ContextFactory.getFactory();
    +    
    +    void reportMyMetric(float myMetric) {
    +        MetricsContext myContext = contextFactory.getContext("myContext");
    +        MetricsRecord myRecord = myContext.getRecord("myRecord");
    +        myRecord.setMetric("myMetric", myMetric);
    +        myRecord.update();
    +    }
    +
    + +In this example there are three names: +
    +
    myContext
    +
    The context name will typically identify either the application, or else a + module within an application or library.
    + +
    myRecord
    +
    The record name generally identifies some entity for which a set of + metrics are to be reported. For example, you could have a record named + "cacheStats" for reporting a number of statistics relating to the usage of + some cache in your application.
    + +
    myMetric
    +
    This identifies a particular metric. For example, you might have metrics + named "cache_hits" and "cache_misses". +
    +
    + +

    Tags

    + +In some cases it is useful to have multiple records with the same name. For +example, suppose that you want to report statistics about each disk on a computer. +In this case, the record name would be something like "diskStats", but you also +need to identify the disk which is done by adding a tag to the record. +The code could look something like this: +
    +    private MetricsRecord diskStats =
    +            contextFactory.getContext("myContext").getRecord("diskStats");
    +            
    +    void reportDiskMetrics(String diskName, float diskBusy, float diskUsed) {
    +        diskStats.setTag("diskName", diskName);
    +        diskStats.setMetric("diskBusy", diskBusy);
    +        diskStats.setMetric("diskUsed", diskUsed);
    +        diskStats.update();
    +    }
    +
    + +

    Buffering and Callbacks

    + +Data is not sent immediately to the metrics system when +MetricsRecord.update() is called. Instead it is stored in an +internal table, and the contents of the table are sent periodically. +This can be important for two reasons: +
      +
    1. It means that a programmer is free to put calls to this API in an + inner loop, since updates can be very frequent without slowing down + the application significantly.
    2. +
    3. Some implementations can gain efficiency by combining many metrics + into a single UDP message.
    4. +
    + +The API provides a timer-based callback via the +registerUpdater() method. The benefit of this +versus using java.util.Timer is that the callbacks will be done +immediately before sending the data, making the data as current as possible. + +

    Configuration

    + +It is possible to programmatically examine and modify configuration data +before creating a context, like this: +
    +    ContextFactory factory = ContextFactory.getFactory();
    +    ... examine and/or modify factory attributes ...
    +    MetricsContext context = factory.getContext("myContext");
    +
    +The factory attributes can be examined and modified using the following +ContextFactorymethods: +
      +
    • Object getAttribute(String attributeName)
    • +
    • String[] getAttributeNames()
    • +
    • void setAttribute(String name, Object value)
    • +
    • void removeAttribute(attributeName)
    • +
    + +

    +ContextFactory.getFactory() initializes the factory attributes by +reading the properties file hadoop-metrics.properties if it exists +on the class path. + +

    +A factory attribute named: +

    +contextName.class
    +
    +should have as its value the fully qualified name of the class to be +instantiated by a call of the CodeFactory method +getContext(contextName). If this factory attribute is not +specified, the default is to instantiate +org.apache.hadoop.metrics.file.FileContext. + +

    +Other factory attributes are specific to a particular implementation of this +API and are documented elsewhere. For example, configuration attributes for +the file and Ganglia implementations can be found in the javadoc for +their respective packages.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Implementation of the metrics package that sends metric data to +Ganglia. +Programmers should not normally need to use this package directly. Instead +they should use org.hadoop.metrics. + +

    +These are the implementation specific factory attributes +(See ContextFactory.getFactory()): + +

    +
    contextName.servers
    +
    Space and/or comma separated sequence of servers to which UDP + messages should be sent.
    + +
    contextName.period
    +
    The period in seconds on which the metric data is sent to the + server(s).
    + +
    contextName.multicast
    +
    Enable multicast for Ganglia
    + +
    contextName.multicast.ttl
    +
    TTL for multicast packets
    + +
    contextName.units.recordName.metricName
    +
    The units for the specified metric in the specified record.
    + +
    contextName.slope.recordName.metricName
    +
    The slope for the specified metric in the specified record.
    + +
    contextName.tmax.recordName.metricName
    +
    The tmax for the specified metric in the specified record.
    + +
    contextName.dmax.recordName.metricName
    +
    The dmax for the specified metric in the specified record.
    + +
    ]]> +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + contextName.tableName. The returned map consists of + those attributes with the contextName and tableName stripped off.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + recordName. + Throws an exception if the metrics implementation is configured with a fixed + set of record names and recordName is not in that set. + + @param recordName the name of the record + @throws MetricsException if recordName conflicts with configuration data]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This class implements the internal table of metric data, and the timer + on which data is to be sent to the metrics system. Subclasses must + override the abstract emitRecord method in order to transmit + the data.

    + + @deprecated Use org.apache.hadoop.metrics2 package instead.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + update + and remove(). + + @deprecated Use {@link org.apache.hadoop.metrics2.impl.MetricsRecordImpl} + instead.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + hostname or hostname:port. If + the specs string is null, defaults to localhost:defaultPort. + + @return a list of InetSocketAddress objects.]]> + + + + + + + + + org.apache.hadoop.metrics.file and +org.apache.hadoop.metrics.ganglia.

    + +Plugging in an implementation involves writing a concrete subclass of +AbstractMetricsContext. The subclass should get its + configuration information using the getAttribute(attributeName) + method.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Implementations of this interface consume the {@link MetricsRecord} generated + from {@link MetricsSource}. It registers with {@link MetricsSystem} which + periodically pushes the {@link MetricsRecord} to the sink using + {@link #putMetrics(MetricsRecord)} method. If the implementing class also + implements {@link Closeable}, then the MetricsSystem will close the sink when + it is stopped.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + the actual type of the source object + @param source object to register + @return the source object + @exception MetricsException]]> + + + + + + + + the actual type of the source object + @param source object to register + @param name of the source. Must be unique or null (then extracted from + the annotations of the source object.) + @param desc the description of the source (or null. See above.) + @return the source object + @exception MetricsException]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + CollectD StatsD plugin). +
    + To configure this plugin, you will need to add the following + entries to your hadoop-metrics2.properties file: +
    +

    + *.sink.statsd.class=org.apache.hadoop.metrics2.sink.StatsDSink
    + [prefix].sink.statsd.server.host=
    + [prefix].sink.statsd.server.port=
    + [prefix].sink.statsd.skip.hostname=true|false (optional)
    + [prefix].sink.statsd.service.name=NameNode (name you want for service)
    + 
    ]]> +
    +
    + +
    + + + + + + + + + + + + + + + ,name=" + Where the and are the supplied parameters + + @param serviceName + @param nameName + @param theMbean - the MBean to register + @return the named used to register the MBean]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + hostname or hostname:port. If + the specs string is null, defaults to localhost:defaultPort. + + @param specs server specs (see description) + @param defaultPort the default port if not specified + @return a list of InetSocketAddress objects.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This method is used when parts of Hadoop need know whether to apply + single rack vs multi-rack policies, such as during block placement. + Such algorithms behave differently if they are on multi-switch systems. +

    + + @return true if the mapping thinks that it is on a single switch]]> +
    +
    + + + + + + + + + + + + + + + + + This predicate simply assumes that all mappings not derived from + this class are multi-switch. + @param mapping the mapping to query + @return true if the base class says it is single switch, or the mapping + is not derived from this class.]]> + + + + It is not mandatory to + derive {@link DNSToSwitchMapping} implementations from it, but it is strongly + recommended, as it makes it easy for the Hadoop developers to add new methods + to this base class that are automatically picked up by all implementations. +

    + + This class does not extend the Configured + base class, and should not be changed to do so, as it causes problems + for subclasses. The constructor of the Configured calls + the {@link #setConf(Configuration)} method, which will call into the + subclasses before they have been fully constructed.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + If a name cannot be resolved to a rack, the implementation + should return {@link NetworkTopology#DEFAULT_RACK}. This + is what the bundled implementations do, though it is not a formal requirement + + @param names the list of hosts to resolve (can be empty) + @return list of resolved network paths. + If names is empty, the returned list is also empty]]> + + + + + + + + + + + + + + + + + + + + + + + + Calling {@link #setConf(Configuration)} will trigger a + re-evaluation of the configuration settings and so be used to + set up the mapping script.]]> + + + + + + + + + + + + + + + + + + + + + This will get called in the superclass constructor, so a check is needed + to ensure that the raw mapping is defined before trying to relaying a null + configuration. + @param conf]]> + + + + + + + + + + It contains a static class RawScriptBasedMapping that performs + the work: reading the configuration parameters, executing any defined + script, handling errors and such like. The outer + class extends {@link CachedDNSToSwitchMapping} to cache the delegated + queries. +

    + This DNS mapper's {@link #isSingleSwitch()} predicate returns + true if and only if a script is defined.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Simple {@link DNSToSwitchMapping} implementation that reads a 2 column text + file. The columns are separated by whitespace. The first column is a DNS or + IP address and the second column specifies the rack where the address maps. +

    +

    + This class uses the configuration parameter {@code + net.topology.table.file.name} to locate the mapping file. +

    +

    + Calls to {@link #resolve(List)} will look up the address as defined in the + mapping file. If no entry corresponding to the address is found, the value + {@code /default-rack} is returned. +

    ]]> +
    +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Avro.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Avro.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + =} getCount(). + @param newCapacity The new capacity in bytes.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Avro.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Avro.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Avro.]]> + + + + + + + + + + + + Index idx = startVector(...); + while (!idx.done()) { + .... // read element of a vector + idx.incr(); + } + + + @deprecated Replaced by Avro.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Avro.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + Avro.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Avro.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Avro.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Avro.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Avro.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Avro.]]> + + + + + + (DEPRECATED) Hadoop record I/O contains classes and a record description language + translator for simplifying serialization and deserialization of records in a + language-neutral manner. +

    + +

    + DEPRECATED: Replaced by Avro. +

    + +

    Introduction

    + + Software systems of any significant complexity require mechanisms for data +interchange with the outside world. These interchanges typically involve the +marshaling and unmarshaling of logical units of data to and from data streams +(files, network connections, memory buffers etc.). Applications usually have +some code for serializing and deserializing the data types that they manipulate +embedded in them. The work of serialization has several features that make +automatic code generation for it worthwhile. Given a particular output encoding +(binary, XML, etc.), serialization of primitive types and simple compositions +of primitives (structs, vectors etc.) is a very mechanical task. Manually +written serialization code can be susceptible to bugs especially when records +have a large number of fields or a record definition changes between software +versions. Lastly, it can be very useful for applications written in different +programming languages to be able to share and interchange data. This can be +made a lot easier by describing the data records manipulated by these +applications in a language agnostic manner and using the descriptions to derive +implementations of serialization in multiple target languages. + +This document describes Hadoop Record I/O, a mechanism that is aimed +at +
      +
    • enabling the specification of simple serializable data types (records) +
    • enabling the generation of code in multiple target languages for +marshaling and unmarshaling such types +
    • providing target language specific support that will enable application +programmers to incorporate generated code into their applications +
    + +The goals of Hadoop Record I/O are similar to those of mechanisms such as XDR, +ASN.1, PADS and ICE. While these systems all include a DDL that enables +the specification of most record types, they differ widely in what else they +focus on. The focus in Hadoop Record I/O is on data marshaling and +multi-lingual support. We take a translator-based approach to serialization. +Hadoop users have to describe their data in a simple data description +language. The Hadoop DDL translator rcc generates code that users +can invoke in order to read/write their data from/to simple stream +abstractions. Next we list explicitly some of the goals and non-goals of +Hadoop Record I/O. + + +

    Goals

    + +
      +
    • Support for commonly used primitive types. Hadoop should include as +primitives commonly used builtin types from programming languages we intend to +support. + +
    • Support for common data compositions (including recursive compositions). +Hadoop should support widely used composite types such as structs and +vectors. + +
    • Code generation in multiple target languages. Hadoop should be capable of +generating serialization code in multiple target languages and should be +easily extensible to new target languages. The initial target languages are +C++ and Java. + +
    • Support for generated target languages. Hadooop should include support +in the form of headers, libraries, packages for supported target languages +that enable easy inclusion and use of generated code in applications. + +
    • Support for multiple output encodings. Candidates include +packed binary, comma-separated text, XML etc. + +
    • Support for specifying record types in a backwards/forwards compatible +manner. This will probably be in the form of support for optional fields in +records. This version of the document does not include a description of the +planned mechanism, we intend to include it in the next iteration. + +
    + +

    Non-Goals

    + +
      +
    • Serializing existing arbitrary C++ classes. +
    • Serializing complex data structures such as trees, linked lists etc. +
    • Built-in indexing schemes, compression, or check-sums. +
    • Dynamic construction of objects from an XML schema. +
    + +The remainder of this document describes the features of Hadoop record I/O +in more detail. Section 2 describes the data types supported by the system. +Section 3 lays out the DDL syntax with some examples of simple records. +Section 4 describes the process of code generation with rcc. Section 5 +describes target language mappings and support for Hadoop types. We include a +fairly complete description of C++ mappings with intent to include Java and +others in upcoming iterations of this document. The last section talks about +supported output encodings. + + +

    Data Types and Streams

    + +This section describes the primitive and composite types supported by Hadoop. +We aim to support a set of types that can be used to simply and efficiently +express a wide range of record types in different programming languages. + +

    Primitive Types

    + +For the most part, the primitive types of Hadoop map directly to primitive +types in high level programming languages. Special cases are the +ustring (a Unicode string) and buffer types, which we believe +find wide use and which are usually implemented in library code and not +available as language built-ins. Hadoop also supplies these via library code +when a target language built-in is not present and there is no widely +adopted "standard" implementation. The complete list of primitive types is: + +
      +
    • byte: An 8-bit unsigned integer. +
    • boolean: A boolean value. +
    • int: A 32-bit signed integer. +
    • long: A 64-bit signed integer. +
    • float: A single precision floating point number as described by + IEEE-754. +
    • double: A double precision floating point number as described by + IEEE-754. +
    • ustring: A string consisting of Unicode characters. +
    • buffer: An arbitrary sequence of bytes. +
    + + +

    Composite Types

    +Hadoop supports a small set of composite types that enable the description +of simple aggregate types and containers. A composite type is serialized +by sequentially serializing it constituent elements. The supported +composite types are: + +
      + +
    • record: An aggregate type like a C-struct. This is a list of +typed fields that are together considered a single unit of data. A record +is serialized by sequentially serializing its constituent fields. In addition +to serialization a record has comparison operations (equality and less-than) +implemented for it, these are defined as memberwise comparisons. + +
    • vector: A sequence of entries of the same data type, primitive +or composite. + +
    • map: An associative container mapping instances of a key type to +instances of a value type. The key and value types may themselves be primitive +or composite types. + +
    + +

    Streams

    + +Hadoop generates code for serializing and deserializing record types to +abstract streams. For each target language Hadoop defines very simple input +and output stream interfaces. Application writers can usually develop +concrete implementations of these by putting a one method wrapper around +an existing stream implementation. + + +

    DDL Syntax and Examples

    + +We now describe the syntax of the Hadoop data description language. This is +followed by a few examples of DDL usage. + +

    Hadoop DDL Syntax

    + +
    
    +recfile = *include module *record
    +include = "include" path
    +path = (relative-path / absolute-path)
    +module = "module" module-name
    +module-name = name *("." name)
    +record := "class" name "{" 1*(field) "}"
    +field := type name ";"
    +name :=  ALPHA (ALPHA / DIGIT / "_" )*
    +type := (ptype / ctype)
    +ptype := ("byte" / "boolean" / "int" |
    +          "long" / "float" / "double"
    +          "ustring" / "buffer")
    +ctype := (("vector" "<" type ">") /
    +          ("map" "<" type "," type ">" ) ) / name)
    +
    + +A DDL file describes one or more record types. It begins with zero or +more include declarations, a single mandatory module declaration +followed by zero or more class declarations. The semantics of each of +these declarations are described below: + +
      + +
    • include: An include declaration specifies a DDL file to be +referenced when generating code for types in the current DDL file. Record types +in the current compilation unit may refer to types in all included files. +File inclusion is recursive. An include does not trigger code +generation for the referenced file. + +
    • module: Every Hadoop DDL file must have a single module +declaration that follows the list of includes and precedes all record +declarations. A module declaration identifies a scope within which +the names of all types in the current file are visible. Module names are +mapped to C++ namespaces, Java packages etc. in generated code. + +
    • class: Records types are specified through class +declarations. A class declaration is like a Java class declaration. +It specifies a named record type and a list of fields that constitute records +of the type. Usage is illustrated in the following examples. + +
    + +

    Examples

    + +
      +
    • A simple DDL file links.jr with just one record declaration. +
      
      +module links {
      +    class Link {
      +        ustring URL;
      +        boolean isRelative;
      +        ustring anchorText;
      +    };
      +}
      +
      + +
    • A DDL file outlinks.jr which includes another +
      
      +include "links.jr"
      +
      +module outlinks {
      +    class OutLinks {
      +        ustring baseURL;
      +        vector outLinks;
      +    };
      +}
      +
      +
    + +

    Code Generation

    + +The Hadoop translator is written in Java. Invocation is done by executing a +wrapper shell script named named rcc. It takes a list of +record description files as a mandatory argument and an +optional language argument (the default is Java) --language or +-l. Thus a typical invocation would look like: +
    
    +$ rcc -l C++  ...
    +
    + + +

    Target Language Mappings and Support

    + +For all target languages, the unit of code generation is a record type. +For each record type, Hadoop generates code for serialization and +deserialization, record comparison and access to record members. + +

    C++

    + +Support for including Hadoop generated C++ code in applications comes in the +form of a header file recordio.hh which needs to be included in source +that uses Hadoop types and a library librecordio.a which applications need +to be linked with. The header declares the Hadoop C++ namespace which defines +appropriate types for the various primitives, the basic interfaces for +records and streams and enumerates the supported serialization encodings. +Declarations of these interfaces and a description of their semantics follow: + +
    
    +namespace hadoop {
    +
    +  enum RecFormat { kBinary, kXML, kCSV };
    +
    +  class InStream {
    +  public:
    +    virtual ssize_t read(void *buf, size_t n) = 0;
    +  };
    +
    +  class OutStream {
    +  public:
    +    virtual ssize_t write(const void *buf, size_t n) = 0;
    +  };
    +
    +  class IOError : public runtime_error {
    +  public:
    +    explicit IOError(const std::string& msg);
    +  };
    +
    +  class IArchive;
    +  class OArchive;
    +
    +  class RecordReader {
    +  public:
    +    RecordReader(InStream& in, RecFormat fmt);
    +    virtual ~RecordReader(void);
    +
    +    virtual void read(Record& rec);
    +  };
    +
    +  class RecordWriter {
    +  public:
    +    RecordWriter(OutStream& out, RecFormat fmt);
    +    virtual ~RecordWriter(void);
    +
    +    virtual void write(Record& rec);
    +  };
    +
    +
    +  class Record {
    +  public:
    +    virtual std::string type(void) const = 0;
    +    virtual std::string signature(void) const = 0;
    +  protected:
    +    virtual bool validate(void) const = 0;
    +
    +    virtual void
    +    serialize(OArchive& oa, const std::string& tag) const = 0;
    +
    +    virtual void
    +    deserialize(IArchive& ia, const std::string& tag) = 0;
    +  };
    +}
    +
    + +
      + +
    • RecFormat: An enumeration of the serialization encodings supported +by this implementation of Hadoop. + +
    • InStream: A simple abstraction for an input stream. This has a +single public read method that reads n bytes from the stream into +the buffer buf. Has the same semantics as a blocking read system +call. Returns the number of bytes read or -1 if an error occurs. + +
    • OutStream: A simple abstraction for an output stream. This has a +single write method that writes n bytes to the stream from the +buffer buf. Has the same semantics as a blocking write system +call. Returns the number of bytes written or -1 if an error occurs. + +
    • RecordReader: A RecordReader reads records one at a time from +an underlying stream in a specified record format. The reader is instantiated +with a stream and a serialization format. It has a read method that +takes an instance of a record and deserializes the record from the stream. + +
    • RecordWriter: A RecordWriter writes records one at a +time to an underlying stream in a specified record format. The writer is +instantiated with a stream and a serialization format. It has a +write method that takes an instance of a record and serializes the +record to the stream. + +
    • Record: The base class for all generated record types. This has two +public methods type and signature that return the typename and the +type signature of the record. + +
    + +Two files are generated for each record file (note: not for each record). If a +record file is named "name.jr", the generated files are +"name.jr.cc" and "name.jr.hh" containing serialization +implementations and record type declarations respectively. + +For each record in the DDL file, the generated header file will contain a +class definition corresponding to the record type, method definitions for the +generated type will be present in the '.cc' file. The generated class will +inherit from the abstract class hadoop::Record. The DDL files +module declaration determines the namespace the record belongs to. +Each '.' delimited token in the module declaration results in the +creation of a namespace. For instance, the declaration module docs.links +results in the creation of a docs namespace and a nested +docs::links namespace. In the preceding examples, the Link class +is placed in the links namespace. The header file corresponding to +the links.jr file will contain: + +
    
    +namespace links {
    +  class Link : public hadoop::Record {
    +    // ....
    +  };
    +};
    +
    + +Each field within the record will cause the generation of a private member +declaration of the appropriate type in the class declaration, and one or more +acccessor methods. The generated class will implement the serialize and +deserialize methods defined in hadoop::Record+. It will also +implement the inspection methods type and signature from +hadoop::Record. A default constructor and virtual destructor will also +be generated. Serialization code will read/write records into streams that +implement the hadoop::InStream and the hadoop::OutStream interfaces. + +For each member of a record an accessor method is generated that returns +either the member or a reference to the member. For members that are returned +by value, a setter method is also generated. This is true for primitive +data members of the types byte, int, long, boolean, float and +double. For example, for a int field called MyField the folowing +code is generated. + +
    
    +...
    +private:
    +  int32_t mMyField;
    +  ...
    +public:
    +  int32_t getMyField(void) const {
    +    return mMyField;
    +  };
    +
    +  void setMyField(int32_t m) {
    +    mMyField = m;
    +  };
    +  ...
    +
    + +For a ustring or buffer or composite field. The generated code +only contains accessors that return a reference to the field. A const +and a non-const accessor are generated. For example: + +
    
    +...
    +private:
    +  std::string mMyBuf;
    +  ...
    +public:
    +
    +  std::string& getMyBuf() {
    +    return mMyBuf;
    +  };
    +
    +  const std::string& getMyBuf() const {
    +    return mMyBuf;
    +  };
    +  ...
    +
    + +

    Examples

    + +Suppose the inclrec.jr file contains: +
    
    +module inclrec {
    +    class RI {
    +        int      I32;
    +        double   D;
    +        ustring  S;
    +    };
    +}
    +
    + +and the testrec.jr file contains: + +
    
    +include "inclrec.jr"
    +module testrec {
    +    class R {
    +        vector VF;
    +        RI            Rec;
    +        buffer        Buf;
    +    };
    +}
    +
    + +Then the invocation of rcc such as: +
    
    +$ rcc -l c++ inclrec.jr testrec.jr
    +
    +will result in generation of four files: +inclrec.jr.{cc,hh} and testrec.jr.{cc,hh}. + +The inclrec.jr.hh will contain: + +
    
    +#ifndef _INCLREC_JR_HH_
    +#define _INCLREC_JR_HH_
    +
    +#include "recordio.hh"
    +
    +namespace inclrec {
    +  
    +  class RI : public hadoop::Record {
    +
    +  private:
    +
    +    int32_t      I32;
    +    double       D;
    +    std::string  S;
    +
    +  public:
    +
    +    RI(void);
    +    virtual ~RI(void);
    +
    +    virtual bool operator==(const RI& peer) const;
    +    virtual bool operator<(const RI& peer) const;
    +
    +    virtual int32_t getI32(void) const { return I32; }
    +    virtual void setI32(int32_t v) { I32 = v; }
    +
    +    virtual double getD(void) const { return D; }
    +    virtual void setD(double v) { D = v; }
    +
    +    virtual std::string& getS(void) const { return S; }
    +    virtual const std::string& getS(void) const { return S; }
    +
    +    virtual std::string type(void) const;
    +    virtual std::string signature(void) const;
    +
    +  protected:
    +
    +    virtual void serialize(hadoop::OArchive& a) const;
    +    virtual void deserialize(hadoop::IArchive& a);
    +  };
    +} // end namespace inclrec
    +
    +#endif /* _INCLREC_JR_HH_ */
    +
    +
    + +The testrec.jr.hh file will contain: + + +
    
    +
    +#ifndef _TESTREC_JR_HH_
    +#define _TESTREC_JR_HH_
    +
    +#include "inclrec.jr.hh"
    +
    +namespace testrec {
    +  class R : public hadoop::Record {
    +
    +  private:
    +
    +    std::vector VF;
    +    inclrec::RI        Rec;
    +    std::string        Buf;
    +
    +  public:
    +
    +    R(void);
    +    virtual ~R(void);
    +
    +    virtual bool operator==(const R& peer) const;
    +    virtual bool operator<(const R& peer) const;
    +
    +    virtual std::vector& getVF(void) const;
    +    virtual const std::vector& getVF(void) const;
    +
    +    virtual std::string& getBuf(void) const ;
    +    virtual const std::string& getBuf(void) const;
    +
    +    virtual inclrec::RI& getRec(void) const;
    +    virtual const inclrec::RI& getRec(void) const;
    +    
    +    virtual bool serialize(hadoop::OutArchive& a) const;
    +    virtual bool deserialize(hadoop::InArchive& a);
    +    
    +    virtual std::string type(void) const;
    +    virtual std::string signature(void) const;
    +  };
    +}; // end namespace testrec
    +#endif /* _TESTREC_JR_HH_ */
    +
    +
    + +

    Java

    + +Code generation for Java is similar to that for C++. A Java class is generated +for each record type with private members corresponding to the fields. Getters +and setters for fields are also generated. Some differences arise in the +way comparison is expressed and in the mapping of modules to packages and +classes to files. For equality testing, an equals method is generated +for each record type. As per Java requirements a hashCode method is also +generated. For comparison a compareTo method is generated for each +record type. This has the semantics as defined by the Java Comparable +interface, that is, the method returns a negative integer, zero, or a positive +integer as the invoked object is less than, equal to, or greater than the +comparison parameter. + +A .java file is generated per record type as opposed to per DDL +file as in C++. The module declaration translates to a Java +package declaration. The module name maps to an identical Java package +name. In addition to this mapping, the DDL compiler creates the appropriate +directory hierarchy for the package and places the generated .java +files in the correct directories. + +

    Mapping Summary

    + +
    
    +DDL Type        C++ Type            Java Type 
    +
    +boolean         bool                boolean
    +byte            int8_t              byte
    +int             int32_t             int
    +long            int64_t             long
    +float           float               float
    +double          double              double
    +ustring         std::string         java.lang.String
    +buffer          std::string         org.apache.hadoop.record.Buffer
    +class type      class type          class type
    +vector    std::vector   java.util.ArrayList
    +map  std::map java.util.TreeMap
    +
    + +

    Data encodings

    + +This section describes the format of the data encodings supported by Hadoop. +Currently, three data encodings are supported, namely binary, CSV and XML. + +

    Binary Serialization Format

    + +The binary data encoding format is fairly dense. Serialization of composite +types is simply defined as a concatenation of serializations of the constituent +elements (lengths are included in vectors and maps). + +Composite types are serialized as follows: +
      +
    • class: Sequence of serialized members. +
    • vector: The number of elements serialized as an int. Followed by a +sequence of serialized elements. +
    • map: The number of key value pairs serialized as an int. Followed +by a sequence of serialized (key,value) pairs. +
    + +Serialization of primitives is more interesting, with a zero compression +optimization for integral types and normalization to UTF-8 for strings. +Primitive types are serialized as follows: + +
      +
    • byte: Represented by 1 byte, as is. +
    • boolean: Represented by 1-byte (0 or 1) +
    • int/long: Integers and longs are serialized zero compressed. +Represented as 1-byte if -120 <= value < 128. Otherwise, serialized as a +sequence of 2-5 bytes for ints, 2-9 bytes for longs. The first byte represents +the number of trailing bytes, N, as the negative number (-120-N). For example, +the number 1024 (0x400) is represented by the byte sequence 'x86 x04 x00'. +This doesn't help much for 4-byte integers but does a reasonably good job with +longs without bit twiddling. +
    • float/double: Serialized in IEEE 754 single and double precision +format in network byte order. This is the format used by Java. +
    • ustring: Serialized as 4-byte zero compressed length followed by +data encoded as UTF-8. Strings are normalized to UTF-8 regardless of native +language representation. +
    • buffer: Serialized as a 4-byte zero compressed length followed by the +raw bytes in the buffer. +
    + + +

    CSV Serialization Format

    + +The CSV serialization format has a lot more structure than the "standard" +Excel CSV format, but we believe the additional structure is useful because + +
      +
    • it makes parsing a lot easier without detracting too much from legibility +
    • the delimiters around composites make it obvious when one is reading a +sequence of Hadoop records +
    + +Serialization formats for the various types are detailed in the grammar that +follows. The notable feature of the formats is the use of delimiters for +indicating the certain field types. + +
      +
    • A string field begins with a single quote ('). +
    • A buffer field begins with a sharp (#). +
    • A class, vector or map begins with 's{', 'v{' or 'm{' respectively and +ends with '}'. +
    + +The CSV format can be described by the following grammar: + +
    
    +record = primitive / struct / vector / map
    +primitive = boolean / int / long / float / double / ustring / buffer
    +
    +boolean = "T" / "F"
    +int = ["-"] 1*DIGIT
    +long = ";" ["-"] 1*DIGIT
    +float = ["-"] 1*DIGIT "." 1*DIGIT ["E" / "e" ["-"] 1*DIGIT]
    +double = ";" ["-"] 1*DIGIT "." 1*DIGIT ["E" / "e" ["-"] 1*DIGIT]
    +
    +ustring = "'" *(UTF8 char except NULL, LF, % and , / "%00" / "%0a" / "%25" / "%2c" )
    +
    +buffer = "#" *(BYTE except NULL, LF, % and , / "%00" / "%0a" / "%25" / "%2c" )
    +
    +struct = "s{" record *("," record) "}"
    +vector = "v{" [record *("," record)] "}"
    +map = "m{" [*(record "," record)] "}"
    +
    + +

    XML Serialization Format

    + +The XML serialization format is the same used by Apache XML-RPC +(http://ws.apache.org/xmlrpc/types.html). This is an extension of the original +XML-RPC format and adds some additional data types. All record I/O types are +not directly expressible in this format, and access to a DDL is required in +order to convert these to valid types. All types primitive or composite are +represented by <value> elements. The particular XML-RPC type is +indicated by a nested element in the <value> element. The encoding for +records is always UTF-8. Primitive types are serialized as follows: + +
      +
    • byte: XML tag <ex:i1>. Values: 1-byte unsigned +integers represented in US-ASCII +
    • boolean: XML tag <boolean>. Values: "0" or "1" +
    • int: XML tags <i4> or <int>. Values: 4-byte +signed integers represented in US-ASCII. +
    • long: XML tag <ex:i8>. Values: 8-byte signed integers +represented in US-ASCII. +
    • float: XML tag <ex:float>. Values: Single precision +floating point numbers represented in US-ASCII. +
    • double: XML tag <double>. Values: Double precision +floating point numbers represented in US-ASCII. +
    • ustring: XML tag <;string>. Values: String values +represented as UTF-8. XML does not permit all Unicode characters in literal +data. In particular, NULLs and control chars are not allowed. Additionally, +XML processors are required to replace carriage returns with line feeds and to +replace CRLF sequences with line feeds. Programming languages that we work +with do not impose these restrictions on string types. To work around these +restrictions, disallowed characters and CRs are percent escaped in strings. +The '%' character is also percent escaped. +
    • buffer: XML tag <string&>. Values: Arbitrary binary +data. Represented as hexBinary, each byte is replaced by its 2-byte +hexadecimal representation. +
    + +Composite types are serialized as follows: + +
      +
    • class: XML tag <struct>. A struct is a sequence of +<member> elements. Each <member> element has a <name> +element and a <value> element. The <name> is a string that must +match /[a-zA-Z][a-zA-Z0-9_]*/. The value of the member is represented +by a <value> element. + +
    • vector: XML tag <array<. An <array> contains a +single <data> element. The <data> element is a sequence of +<value> elements each of which represents an element of the vector. + +
    • map: XML tag <array>. Same as vector. + +
    + +For example: + +
    
    +class {
    +  int           MY_INT;            // value 5
    +  vector MY_VEC;            // values 0.1, -0.89, 2.45e4
    +  buffer        MY_BUF;            // value '\00\n\tabc%'
    +}
    +
    + +is serialized as + +
    
    +<value>
    +  <struct>
    +    <member>
    +      <name>MY_INT</name>
    +      <value><i4>5</i4></value>
    +    </member>
    +    <member>
    +      <name>MY_VEC</name>
    +      <value>
    +        <array>
    +          <data>
    +            <value><ex:float>0.1</ex:float></value>
    +            <value><ex:float>-0.89</ex:float></value>
    +            <value><ex:float>2.45e4</ex:float></value>
    +          </data>
    +        </array>
    +      </value>
    +    </member>
    +    <member>
    +      <name>MY_BUF</name>
    +      <value><string>%00\n\tabc%25</string></value>
    +    </member>
    +  </struct>
    +</value> 
    +
    ]]> +
    +
    + + + + + + + Avro.]]> + + + + + + + + + + + + + + + + + + + + + Avro.]]> + + + + + + + + + + + + Avro.]]> + + + + + + + + + + + + Avro.]]> + + + + + + + + + Avro.]]> + + + + + + + + + + + + Avro.]]> + + + + + + + + + + + + Avro.]]> + + + + + + + + + + + + + + + + + + + + + Avro.]]> + + + + + + + + + + + + Avro.]]> + + + + + + + + + + + + Avro.]]> + + + + + + + + + + + + Avro.]]> + + + + + + + + + + + + Avro.]]> + + + + + + + + + + + + Avro.]]> + + + + + + + + + + + + Avro.]]> + + + + + + + + + Avro.]]> + + + + + + + + + + + + Avro.]]> + + + + + + (DEPRECATED) This package contains classes needed for code generation + from the hadoop record compiler. CppGenerator and JavaGenerator + are the main entry points from the parser. There are classes + corrsponding to every primitive type and compound type + included in Hadoop record I/O syntax. +

    + +

    + DEPRECATED: Replaced by Avro. +

    ]]> +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This task takes the given record definition files and compiles them into + java or c++ + files. It is then up to the user to compile the generated files. + +

    The task requires the file or the nested fileset element to be + specified. Optional attributes are language (set the output + language, default is "java"), + destdir (name of the destination directory for generated java/c++ + code, default is ".") and failonerror (specifies error handling + behavior. default is true). +

    Usage

    +
    + <recordcc
    +       destdir="${basedir}/gensrc"
    +       language="java">
    +   <fileset include="**\/*.jr" />
    + </recordcc>
    + 
    + + @deprecated Replaced by Avro.]]> +
    +
    + +
    + + + + + + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Avro.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Avro.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Avro.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Avro.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Avro.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Avro.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Avro.]]> + + + + + + (DEPRECATED) This package contains code generated by JavaCC from the + Hadoop record syntax file rcc.jj. For details about the + record file syntax please @see org.apache.hadoop.record. +

    + +

    + DEPRECATED: Replaced by Avro. +

    ]]> +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Avro.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Avro.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Avro.]]> + + + + + + + + + + + + + + + + + + + Avro.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Avro.]]> + + + + + + + + + + + + + + + + Avro.]]> + + + + + + + + + + + + + + + + + + + + + + Avro.]]> + + + + + + + + + + + + + + + + + + + + (cause==null ? null : cause.toString()) (which + typically contains the class and detail message of cause). + @param cause the cause (which is saved for later retrieval by the + {@link #getCause()} method). (A null value is + permitted, and indicates that the cause is nonexistent or + unknown.)]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + mapping + and mapping]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + /host@realm. + @param principalName principal name of format as described above + @return host name if the the string conforms to the above format, else null]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "jack" + + @param userName + @return userName without login method]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + the return type of the run method + @param action the method to execute + @return the value from the run method]]> + + + + + + + + the return type of the run method + @param action the method to execute + @return the value from the run method + @throws IOException if the action throws an IOException + @throws Error if the action throws an Error + @throws RuntimeException if the action throws a RuntimeException + @throws InterruptedException if the action throws an InterruptedException + @throws UndeclaredThrowableException if the action throws something else]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + (cause==null ? null : cause.toString()) (which + typically contains the class and detail message of cause). + @param cause the cause (which is saved for later retrieval by the + {@link #getCause()} method). (A null value is + permitted, and indicates that the cause is nonexistent or + unknown.)]]> + + + + + + + + + + + + + + does not provide the stack trace for security purposes.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A User-Agent String is considered to be a browser if it matches + any of the regex patterns from browser-useragent-regex; the default + behavior is to consider everything a browser that matches the following: + "^Mozilla.*,^Opera.*". Subclasses can optionally override + this method to use different behavior. + + @param userAgent The User-Agent String, or null if there isn't one + @return true if the User-Agent String refers to a browser, false if not]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The type of the token identifier]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + T extends TokenIdentifier]]> + + + + + + + + + + DelegationTokenAuthenticatedURL. +

    + An instance of the default {@link DelegationTokenAuthenticator} will be + used.]]> + + + + + DelegationTokenAuthenticatedURL. + + @param authenticator the {@link DelegationTokenAuthenticator} instance to + use, if null the default one will be used.]]> + + + + + DelegationTokenAuthenticatedURL using the default + {@link DelegationTokenAuthenticator} class. + + @param connConfigurator a connection configurator.]]> + + + + + DelegationTokenAuthenticatedURL. + + @param authenticator the {@link DelegationTokenAuthenticator} instance to + use, if null the default one will be used. + @param connConfigurator a connection configurator.]]> + + + + + + + + + + + + The default class is {@link KerberosDelegationTokenAuthenticator} + + @return the delegation token authenticator class to use as default.]]> + + + + + + + This method is provided to enable WebHDFS backwards compatibility. + + @param useQueryString TRUE if the token is transmitted in the + URL query string, FALSE if the delegation token is transmitted + using the {@link DelegationTokenAuthenticator#DELEGATION_TOKEN_HEADER} HTTP + header.]]> + + + + + TRUE if the token is transmitted in the URL query + string, FALSE if the delegation token is transmitted using the + {@link DelegationTokenAuthenticator#DELEGATION_TOKEN_HEADER} HTTP header.]]> + + + + + + + + + + + + + + + + + + Authenticator. + + @param url the URL to connect to. Only HTTP/S URLs are supported. + @param token the authentication token being used for the user. + @return an authenticated {@link HttpURLConnection}. + @throws IOException if an IO error occurred. + @throws AuthenticationException if an authentication exception occurred.]]> + + + + + + + + + + Authenticator. If the doAs parameter is not NULL, + the request will be done on behalf of the specified doAs user. + + @param url the URL to connect to. Only HTTP/S URLs are supported. + @param token the authentication token being used for the user. + @param doAs user to do the the request on behalf of, if NULL the request is + as self. + @return an authenticated {@link HttpURLConnection}. + @throws IOException if an IO error occurred. + @throws AuthenticationException if an authentication exception occurred.]]> + + + + + + + + + + Authenticator + for authentication. + + @param url the URL to get the delegation token from. Only HTTP/S URLs are + supported. + @param token the authentication token being used for the user where the + Delegation token will be stored. + @param renewer the renewer user. + @return a delegation token. + @throws IOException if an IO error occurred. + @throws AuthenticationException if an authentication exception occurred.]]> + + + + + + + + + + + Authenticator + for authentication. + + @param url the URL to get the delegation token from. Only HTTP/S URLs are + supported. + @param token the authentication token being used for the user where the + Delegation token will be stored. + @param renewer the renewer user. + @param doAsUser the user to do as, which will be the token owner. + @return a delegation token. + @throws IOException if an IO error occurred. + @throws AuthenticationException if an authentication exception occurred.]]> + + + + + + + + + Authenticator for authentication. + + @param url the URL to renew the delegation token from. Only HTTP/S URLs are + supported. + @param token the authentication token with the Delegation Token to renew. + @throws IOException if an IO error occurred. + @throws AuthenticationException if an authentication exception occurred.]]> + + + + + + + + + + Authenticator for authentication. + + @param url the URL to renew the delegation token from. Only HTTP/S URLs are + supported. + @param token the authentication token with the Delegation Token to renew. + @param doAsUser the user to do as, which will be the token owner. + @throws IOException if an IO error occurred. + @throws AuthenticationException if an authentication exception occurred.]]> + + + + + + + + Authenticator. + + @param url the URL to cancel the delegation token from. Only HTTP/S URLs + are supported. + @param token the authentication token with the Delegation Token to cancel. + @throws IOException if an IO error occurred.]]> + + + + + + + + + Authenticator. + + @param url the URL to cancel the delegation token from. Only HTTP/S URLs + are supported. + @param token the authentication token with the Delegation Token to cancel. + @param doAsUser the user to do as, which will be the token owner. + @throws IOException if an IO error occurred.]]> + + + + DelegationTokenAuthenticatedURL is a + {@link AuthenticatedURL} sub-class with built-in Hadoop Delegation Token + functionality. +

    + The authentication mechanisms supported by default are Hadoop Simple + authentication (also known as pseudo authentication) and Kerberos SPNEGO + authentication. +

    + Additional authentication mechanisms can be supported via {@link + DelegationTokenAuthenticator} implementations. +

    + The default {@link DelegationTokenAuthenticator} is the {@link + KerberosDelegationTokenAuthenticator} class which supports + automatic fallback from Kerberos SPNEGO to Hadoop Simple authentication via + the {@link PseudoDelegationTokenAuthenticator} class. +

    + AuthenticatedURL instances are not thread-safe.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Authenticator + for authentication. + + @param url the URL to get the delegation token from. Only HTTP/S URLs are + supported. + @param token the authentication token being used for the user where the + Delegation token will be stored. + @param renewer the renewer user. + @throws IOException if an IO error occurred. + @throws AuthenticationException if an authentication exception occurred.]]> + + + + + + + + + + + Authenticator + for authentication. + + @param url the URL to get the delegation token from. Only HTTP/S URLs are + supported. + @param token the authentication token being used for the user where the + Delegation token will be stored. + @param renewer the renewer user. + @param doAsUser the user to do as, which will be the token owner. + @throws IOException if an IO error occurred. + @throws AuthenticationException if an authentication exception occurred.]]> + + + + + + + + + + Authenticator for authentication. + + @param url the URL to renew the delegation token from. Only HTTP/S URLs are + supported. + @param token the authentication token with the Delegation Token to renew. + @throws IOException if an IO error occurred. + @throws AuthenticationException if an authentication exception occurred.]]> + + + + + + + + + + + Authenticator for authentication. + + @param url the URL to renew the delegation token from. Only HTTP/S URLs are + supported. + @param token the authentication token with the Delegation Token to renew. + @param doAsUser the user to do as, which will be the token owner. + @throws IOException if an IO error occurred. + @throws AuthenticationException if an authentication exception occurred.]]> + + + + + + + + + Authenticator. + + @param url the URL to cancel the delegation token from. Only HTTP/S URLs + are supported. + @param token the authentication token with the Delegation Token to cancel. + @throws IOException if an IO error occurred.]]> + + + + + + + + + + Authenticator. + + @param url the URL to cancel the delegation token from. Only HTTP/S URLs + are supported. + @param token the authentication token with the Delegation Token to cancel. + @param doAsUser the user to do as, which will be the token owner. + @throws IOException if an IO error occurred.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + KerberosDelegationTokenAuthenticator provides support for + Kerberos SPNEGO authentication mechanism and support for Hadoop Delegation + Token operations. +

    + It falls back to the {@link PseudoDelegationTokenAuthenticator} if the HTTP + endpoint does not trigger a SPNEGO authentication]]> + + + + + + + + + PseudoDelegationTokenAuthenticator provides support for + Hadoop's pseudo authentication mechanism that accepts + the user name specified as a query string parameter and support for Hadoop + Delegation Token operations. +

    + This mimics the model of Hadoop Simple authentication trusting the + {@link UserGroupInformation#getCurrentUser()} value.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + live. + @return a (snapshotted) map of blocker name->description values]]> + + + + + + + + + + + + + Do nothing if the service is null or not + in a state in which it can be/needs to be stopped. +

    + The service state is checked before the operation begins. + This process is not thread safe. + @param service a service or null]]> + + + + + + + + + + + + + + + + + + + + + + + + + + +

  • Any long-lived operation here will prevent the service state + change from completing in a timely manner.
  • +
  • If another thread is somehow invoked from the listener, and + that thread invokes the methods of the service (including + subclass-specific methods), there is a risk of a deadlock.
  • + + + + @param service the service that has changed.]]> +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Clients and/or applications can use the provided Progressable + to explicitly report progress to the Hadoop framework. This is especially + important for operations which take significant amount of time since, + in-lieu of the reported progress, the framework has to assume that an error + has occured and time-out the operation.

    ]]> +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Class is to be obtained + @return the correctly typed Class of the given object.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + kill -0 command or equivalent]]> + + + + + + + + + + + + + + + + + + + ".cmd" on Windows, or ".sh" otherwise. + + @param parent File parent directory + @param basename String script file basename + @return File referencing the script in the directory]]> + + + + + + ".cmd" on Windows, or ".sh" otherwise. + + @param basename String script file basename + @return String script file name]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IOException. + @return the path to {@link #WINUTILS_EXE} + @throws RuntimeException if the path is not resolvable]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Shell interface. + @param cmd shell command to execute. + @return the output of the executed command.]]> + + + + + + + + + Shell interface. + @param env the map of environment key=value + @param cmd shell command to execute. + @param timeout time in milliseconds after which script should be marked timeout + @return the output of the executed command. + @throws IOException on any problem.]]> + + + + + + + + Shell interface. + @param env the map of environment key=value + @param cmd shell command to execute. + @return the output of the executed command. + @throws IOException on any problem.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + CreateProcess synchronization object.]]> + + + + + os.name property.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Important: caller must check for this value being null. + The lack of such checks has led to many support issues being raised. +

    + @deprecated use one of the exception-raising getter methods, + specifically {@link #getWinUtilsPath()} or {@link #getWinUtilsFile()}]]> + + + + + + + + + + + + + + Shell can be used to run shell commands like du or + df. It also offers facilities to gate commands by + time-intervals.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Tool, is the standard for any Map-Reduce tool/application. + The tool/application should delegate the handling of + + standard command-line options to {@link ToolRunner#run(Tool, String[])} + and only handle its custom arguments.

    + +

    Here is how a typical Tool is implemented:

    +

    +     public class MyApp extends Configured implements Tool {
    +     
    +       public int run(String[] args) throws Exception {
    +         // Configuration processed by ToolRunner
    +         Configuration conf = getConf();
    +         
    +         // Create a JobConf using the processed conf
    +         JobConf job = new JobConf(conf, MyApp.class);
    +         
    +         // Process custom command-line options
    +         Path in = new Path(args[1]);
    +         Path out = new Path(args[2]);
    +         
    +         // Specify various job-specific parameters     
    +         job.setJobName("my-app");
    +         job.setInputPath(in);
    +         job.setOutputPath(out);
    +         job.setMapperClass(MyMapper.class);
    +         job.setReducerClass(MyReducer.class);
    +
    +         // Submit the job, then poll for progress until the job is complete
    +         RunningJob runningJob = JobClient.runJob(job);
    +         if (runningJob.isSuccessful()) {
    +           return 0;
    +         } else {
    +           return 1;
    +         }
    +       }
    +       
    +       public static void main(String[] args) throws Exception {
    +         // Let ToolRunner handle generic command-line options 
    +         int res = ToolRunner.run(new Configuration(), new MyApp(), args);
    +         
    +         System.exit(res);
    +       }
    +     }
    + 

    + + @see GenericOptionsParser + @see ToolRunner]]> +
    + + + + + + + + + + + + + Tool by {@link Tool#run(String[])}, after + parsing with the given generic arguments. Uses the given + Configuration, or builds one if null. + + Sets the Tool's configuration with the possibly modified + version of the conf. + + @param conf Configuration for the Tool. + @param tool Tool to run. + @param args command-line arguments to the tool. + @return exit code of the {@link Tool#run(String[])} method.]]> + + + + + + + + Tool with its Configuration. + + Equivalent to run(tool.getConf(), tool, args). + + @param tool Tool to run. + @param args command-line arguments to the tool. + @return exit code of the {@link Tool#run(String[])} method.]]> + + + + + + + + + + + + + + + + + ToolRunner can be used to run classes implementing + Tool interface. It works in conjunction with + {@link GenericOptionsParser} to parse the + + generic hadoop command line arguments and modifies the + Configuration of the Tool. The + application-specific options are passed along without being modified. +

    + + @see Tool + @see GenericOptionsParser]]> +
    +
    + + + + +
    + + + + + + + + + + + this filter. + @param nbHash The number of hash function to consider. + @param hashType type of the hashing function (see + {@link org.apache.hadoop.util.hash.Hash}).]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Bloom filter, as defined by Bloom in 1970. +

    + The Bloom filter is a data structure that was introduced in 1970 and that has been adopted by + the networking research community in the past decade thanks to the bandwidth efficiencies that it + offers for the transmission of set membership information between networked hosts. A sender encodes + the information into a bit vector, the Bloom filter, that is more compact than a conventional + representation. Computation and space costs for construction are linear in the number of elements. + The receiver uses the filter to test whether various elements are members of the set. Though the + filter will occasionally return a false positive, it will never return a false negative. When creating + the filter, the sender can choose its desired point in a trade-off between the false positive rate and the size. + +

    + Originally created by + European Commission One-Lab Project 034819. + + @see Filter The general behavior of a filter + + @see Space/Time Trade-Offs in Hash Coding with Allowable Errors]]> + + + + + + + + + + + + + this filter. + @param nbHash The number of hash function to consider. + @param hashType type of the hashing function (see + {@link org.apache.hadoop.util.hash.Hash}).]]> + + + + + + + + + this counting Bloom filter. +

    + Invariant: nothing happens if the specified key does not belong to this counter Bloom filter. + @param key The key to remove.]]> + + + + + + + + + + + + key -> count map. +

    NOTE: due to the bucket size of this filter, inserting the same + key more than 15 times will cause an overflow at all filter positions + associated with this key, and it will significantly increase the error + rate for this and other keys. For this reason the filter can only be + used to store small count values 0 <= N << 15. + @param key key to be tested + @return 0 if the key is not present. Otherwise, a positive value v will + be returned such that v == count with probability equal to the + error rate of this filter, and v > count otherwise. + Additionally, if the filter experienced an underflow as a result of + {@link #delete(Key)} operation, the return value may be lower than the + count with the probability of the false negative rate of such + filter.]]> + + + + + + + + + + + + + + + + + + + + + + counting Bloom filter, as defined by Fan et al. in a ToN + 2000 paper. +

    + A counting Bloom filter is an improvement to standard a Bloom filter as it + allows dynamic additions and deletions of set membership information. This + is achieved through the use of a counting vector instead of a bit vector. +

    + Originally created by + European Commission One-Lab Project 034819. + + @see Filter The general behavior of a filter + + @see Summary cache: a scalable wide-area web cache sharing protocol]]> + + + + + + + + + + + + + + Builds an empty Dynamic Bloom filter. + @param vectorSize The number of bits in the vector. + @param nbHash The number of hash function to consider. + @param hashType type of the hashing function (see + {@link org.apache.hadoop.util.hash.Hash}). + @param nr The threshold for the maximum number of keys to record in a + dynamic Bloom filter row.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dynamic Bloom filter, as defined in the INFOCOM 2006 paper. +

    + A dynamic Bloom filter (DBF) makes use of a s * m bit matrix but + each of the s rows is a standard Bloom filter. The creation + process of a DBF is iterative. At the start, the DBF is a 1 * m + bit matrix, i.e., it is composed of a single standard Bloom filter. + It assumes that nr elements are recorded in the + initial bit vector, where nr <= n (n is + the cardinality of the set A to record in the filter). +

    + As the size of A grows during the execution of the application, + several keys must be inserted in the DBF. When inserting a key into the DBF, + one must first get an active Bloom filter in the matrix. A Bloom filter is + active when the number of recorded keys, nr, is + strictly less than the current cardinality of A, n. + If an active Bloom filter is found, the key is inserted and + nr is incremented by one. On the other hand, if there + is no active Bloom filter, a new one is created (i.e., a new row is added to + the matrix) according to the current size of A and the element + is added in this new Bloom filter and the nr value of + this new Bloom filter is set to one. A given key is said to belong to the + DBF if the k positions are set to one in one of the matrix rows. +

    + Originally created by + European Commission One-Lab Project 034819. + + @see Filter The general behavior of a filter + @see BloomFilter A Bloom filter + + @see Theory and Network Applications of Dynamic Bloom Filters]]> + + + + + + + + + Builds a hash function that must obey to a given maximum number of returned values and a highest value. + @param maxValue The maximum highest returned value. + @param nbHash The number of resulting hashed values. + @param hashType type of the hashing function (see {@link Hash}).]]> + + + + + this hash function. A NOOP]]> + + + + + + + + + + + + + + + + + + + The idea is to randomly select a bit to reset.]]> + + + + + + The idea is to select the bit to reset that will generate the minimum + number of false negative.]]> + + + + + + The idea is to select the bit to reset that will remove the maximum number + of false positive.]]> + + + + + + The idea is to select the bit to reset that will, at the same time, remove + the maximum number of false positve while minimizing the amount of false + negative generated.]]> + + + + + Originally created by + European Commission One-Lab Project 034819.]]> + + + + + + + + + + + + + + this filter. + @param nbHash The number of hash function to consider. + @param hashType type of the hashing function (see + {@link org.apache.hadoop.util.hash.Hash}).]]> + + + + + + + + + this retouched Bloom filter. +

    + Invariant: if the false positive is null, nothing happens. + @param key The false positive key to add.]]> + + + + + + this retouched Bloom filter. + @param coll The collection of false positive.]]> + + + + + + this retouched Bloom filter. + @param keys The list of false positive.]]> + + + + + + this retouched Bloom filter. + @param keys The array of false positive.]]> + + + + + + + this retouched Bloom filter. + @param scheme The selective clearing scheme to apply.]]> + + + + + + + + + + + + retouched Bloom filter, as defined in the CoNEXT 2006 paper. +

    + It allows the removal of selected false positives at the cost of introducing + random false negatives, and with the benefit of eliminating some random false + positives at the same time. + +

    + Originally created by + European Commission One-Lab Project 034819. + + @see Filter The general behavior of a filter + @see BloomFilter A Bloom filter + @see RemoveScheme The different selective clearing algorithms + + @see Retouched Bloom Filters: Allowing Networked Applications to Trade Off Selected False Positives Against False Negatives]]> + + + + + + + + + + diff --git a/hadoop-mapreduce-project/dev-support/jdiff/Apache_Hadoop_MapReduce_Common_2.8.0.xml b/hadoop-mapreduce-project/dev-support/jdiff/Apache_Hadoop_MapReduce_Common_2.8.0.xml new file mode 100644 index 00000000000..453bd245492 --- /dev/null +++ b/hadoop-mapreduce-project/dev-support/jdiff/Apache_Hadoop_MapReduce_Common_2.8.0.xml @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hadoop-mapreduce-project/dev-support/jdiff/Apache_Hadoop_MapReduce_Core_2.8.0.xml b/hadoop-mapreduce-project/dev-support/jdiff/Apache_Hadoop_MapReduce_Core_2.8.0.xml new file mode 100644 index 00000000000..4dbf6d691b8 --- /dev/null +++ b/hadoop-mapreduce-project/dev-support/jdiff/Apache_Hadoop_MapReduce_Core_2.8.0.xml @@ -0,0 +1,27490 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + FileStatus of a given cache file on hdfs + @throws IOException]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DistributedCache is a facility provided by the Map-Reduce + framework to cache files (text, archives, jars etc.) needed by applications. +

    + +

    Applications specify the files, via urls (hdfs:// or http://) to be cached + via the {@link org.apache.hadoop.mapred.JobConf}. The + DistributedCache assumes that the files specified via urls are + already present on the {@link FileSystem} at the path specified by the url + and are accessible by every machine in the cluster.

    + +

    The framework will copy the necessary files on to the slave node before + any tasks for the job are executed on that node. Its efficiency stems from + the fact that the files are only copied once per job and the ability to + cache archives which are un-archived on the slaves.

    + +

    DistributedCache can be used to distribute simple, read-only + data/text files and/or more complex types such as archives, jars etc. + Archives (zip, tar and tgz/tar.gz files) are un-archived at the slave nodes. + Jars may be optionally added to the classpath of the tasks, a rudimentary + software distribution mechanism. Files have execution permissions. + In older version of Hadoop Map/Reduce users could optionally ask for symlinks + to be created in the working directory of the child task. In the current + version symlinks are always created. If the URL does not have a fragment + the name of the file or directory will be used. If multiple files or + directories map to the same link name, the last one added, will be used. All + others will not even be downloaded.

    + +

    DistributedCache tracks modification timestamps of the cache + files. Clearly the cache files should not be modified by the application + or externally while the job is executing.

    + +

    Here is an illustrative example on how to use the + DistributedCache:

    +

    +     // Setting up the cache for the application
    +     
    +     1. Copy the requisite files to the FileSystem:
    +     
    +     $ bin/hadoop fs -copyFromLocal lookup.dat /myapp/lookup.dat  
    +     $ bin/hadoop fs -copyFromLocal map.zip /myapp/map.zip  
    +     $ bin/hadoop fs -copyFromLocal mylib.jar /myapp/mylib.jar
    +     $ bin/hadoop fs -copyFromLocal mytar.tar /myapp/mytar.tar
    +     $ bin/hadoop fs -copyFromLocal mytgz.tgz /myapp/mytgz.tgz
    +     $ bin/hadoop fs -copyFromLocal mytargz.tar.gz /myapp/mytargz.tar.gz
    +     
    +     2. Setup the application's JobConf:
    +     
    +     JobConf job = new JobConf();
    +     DistributedCache.addCacheFile(new URI("/myapp/lookup.dat#lookup.dat"), 
    +                                   job);
    +     DistributedCache.addCacheArchive(new URI("/myapp/map.zip", job);
    +     DistributedCache.addFileToClassPath(new Path("/myapp/mylib.jar"), job);
    +     DistributedCache.addCacheArchive(new URI("/myapp/mytar.tar", job);
    +     DistributedCache.addCacheArchive(new URI("/myapp/mytgz.tgz", job);
    +     DistributedCache.addCacheArchive(new URI("/myapp/mytargz.tar.gz", job);
    +     
    +     3. Use the cached files in the {@link org.apache.hadoop.mapred.Mapper}
    +     or {@link org.apache.hadoop.mapred.Reducer}:
    +     
    +     public static class MapClass extends MapReduceBase  
    +     implements Mapper<K, V, K, V> {
    +     
    +       private Path[] localArchives;
    +       private Path[] localFiles;
    +       
    +       public void configure(JobConf job) {
    +         // Get the cached archives/files
    +         File f = new File("./map.zip/some/file/in/zip.txt");
    +       }
    +       
    +       public void map(K key, V value, 
    +                       OutputCollector<K, V> output, Reporter reporter) 
    +       throws IOException {
    +         // Use data from the cached archives/files here
    +         // ...
    +         // ...
    +         output.collect(k, v);
    +       }
    +     }
    +     
    + 
    + + It is also very common to use the DistributedCache by using + {@link org.apache.hadoop.util.GenericOptionsParser}. + + This class includes methods that should be used by users + (specifically those mentioned in the example above, as well + as {@link DistributedCache#addArchiveToClassPath(Path, Configuration)}), + as well as methods intended for use by the MapReduce framework + (e.g., {@link org.apache.hadoop.mapred.JobClient}). + + @see org.apache.hadoop.mapred.JobConf + @see org.apache.hadoop.mapred.JobClient + @see org.apache.hadoop.mapreduce.Job]]> +
    +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + JobTracker, + as {@link JobTracker.State} + + {@link JobTracker.State} should no longer be used on M/R 2.x. The function + is kept to be compatible with M/R 1.x applications. + + @return the invalid state of the JobTracker.]]> + + + + + + + + + + + + + + ClusterStatus provides clients with information such as: +
      +
    1. + Size of the cluster. +
    2. +
    3. + Name of the trackers. +
    4. +
    5. + Task capacity of the cluster. +
    6. +
    7. + The number of currently running map and reduce tasks. +
    8. +
    9. + State of the JobTracker. +
    10. +
    11. + Details regarding black listed trackers. +
    12. +
    + +

    Clients can query for the latest ClusterStatus, via + {@link JobClient#getClusterStatus()}.

    + + @see JobClient]]> +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Counters represent global counters, defined either by the + Map-Reduce framework or applications. Each Counter can be of + any {@link Enum} type.

    + +

    Counters are bunched into {@link Group}s, each comprising of + counters from a particular Enum class.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Group of counters, comprising of counters from a particular + counter {@link Enum} class. + +

    Grouphandles localization of the class name and the + counter names.

    ]]> +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + FileInputFormat always returns + true. Implementations that may deal with non-splittable files must + override this method. + + FileInputFormat implementations can override this and return + false to ensure that individual input files are never split-up + so that {@link Mapper}s process entire files. + + @param fs the file system that the file is on + @param filename the file name to check + @return is this file splitable?]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + FileInputFormat is the base class for all file-based + InputFormats. This provides a generic implementation of + {@link #getSplits(JobConf, int)}. + + Implementations of FileInputFormat can also override the + {@link #isSplitable(FileSystem, Path)} method to prevent input files + from being split-up in certain situations. Implementations that may + deal with non-splittable files must override this method, since + the default implementation assumes splitting is always possible.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true if the job output should be compressed, + false otherwise]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Tasks' Side-Effect Files + +

    Note: The following is valid only if the {@link OutputCommitter} + is {@link FileOutputCommitter}. If OutputCommitter is not + a FileOutputCommitter, the task's temporary output + directory is same as {@link #getOutputPath(JobConf)} i.e. + ${mapreduce.output.fileoutputformat.outputdir}$

    + +

    Some applications need to create/write-to side-files, which differ from + the actual job-outputs. + +

    In such cases there could be issues with 2 instances of the same TIP + (running simultaneously e.g. speculative tasks) trying to open/write-to the + same file (path) on HDFS. Hence the application-writer will have to pick + unique names per task-attempt (e.g. using the attemptid, say + attempt_200709221812_0001_m_000000_0), not just per TIP.

    + +

    To get around this the Map-Reduce framework helps the application-writer + out by maintaining a special + ${mapreduce.output.fileoutputformat.outputdir}/_temporary/_${taskid} + sub-directory for each task-attempt on HDFS where the output of the + task-attempt goes. On successful completion of the task-attempt the files + in the ${mapreduce.output.fileoutputformat.outputdir}/_temporary/_${taskid} (only) + are promoted to ${mapreduce.output.fileoutputformat.outputdir}. Of course, the + framework discards the sub-directory of unsuccessful task-attempts. This + is completely transparent to the application.

    + +

    The application-writer can take advantage of this by creating any + side-files required in ${mapreduce.task.output.dir} during execution + of his reduce-task i.e. via {@link #getWorkOutputPath(JobConf)}, and the + framework will move them out similarly - thus she doesn't have to pick + unique paths per task-attempt.

    + +

    Note: the value of ${mapreduce.task.output.dir} during + execution of a particular task-attempt is actually + ${mapreduce.output.fileoutputformat.outputdir}/_temporary/_{$taskid}, and this value is + set by the map-reduce framework. So, just create any side-files in the + path returned by {@link #getWorkOutputPath(JobConf)} from map/reduce + task to take advantage of this feature.

    + +

    The entire discussion holds true for maps of jobs with + reducer=NONE (i.e. 0 reduces) since output of the map, in that case, + goes directly to HDFS.

    + + @return the {@link Path} to the task's temporary output directory + for the map-reduce job.]]> +
    +
    + + + + + + + + + + + + + The generated name can be used to create custom files from within the + different tasks for the job, the names for different tasks will not collide + with each other.

    + +

    The given name is postfixed with the task type, 'm' for maps, 'r' for + reduces and the task partition number. For example, give a name 'test' + running on the first map o the job the generated name will be + 'test-m-00000'.

    + + @param conf the configuration for the job. + @param name the name to make unique. + @return a unique name accross all tasks of the job.]]> +
    +
    + + + + + The path can be used to create custom files from within the map and + reduce tasks. The path name will be unique for each task. The path parent + will be the job output directory.

    ls + +

    This method uses the {@link #getUniqueName} method to make the file name + unique for the task.

    + + @param conf the configuration for the job. + @param name the name for the file. + @return a unique path accross all tasks of the job.]]> +
    +
    + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    or + conf.setInt(FixedLengthInputFormat.FIXED_RECORD_LENGTH, recordLength); +

    + @see FixedLengthRecordReader]]> +
    +
    + + + + + + + + + + + + + + + + + + + + + + + Each {@link InputSplit} is then assigned to an individual {@link Mapper} + for processing.

    + +

    Note: The split is a logical split of the inputs and the + input files are not physically split into chunks. For e.g. a split could + be <input-file-path, start, offset> tuple. + + @param job job configuration. + @param numSplits the desired number of splits, a hint. + @return an array of {@link InputSplit}s for the job.]]> + + + + + + + + + It is the responsibility of the RecordReader to respect + record boundaries while processing the logical split to present a + record-oriented view to the individual task.

    + + @param split the {@link InputSplit} + @param job the job that this split belongs to + @return a {@link RecordReader}]]> +
    +
    + + InputFormat describes the input-specification for a + Map-Reduce job. + +

    The Map-Reduce framework relies on the InputFormat of the + job to:

    +

      +
    1. + Validate the input-specification of the job. +
    2. + Split-up the input file(s) into logical {@link InputSplit}s, each of + which is then assigned to an individual {@link Mapper}. +
    3. +
    4. + Provide the {@link RecordReader} implementation to be used to glean + input records from the logical InputSplit for processing by + the {@link Mapper}. +
    5. +
    + +

    The default behavior of file-based {@link InputFormat}s, typically + sub-classes of {@link FileInputFormat}, is to split the + input into logical {@link InputSplit}s based on the total size, in + bytes, of the input files. However, the {@link FileSystem} blocksize of + the input files is treated as an upper bound for input splits. A lower bound + on the split size can be set via + + mapreduce.input.fileinputformat.split.minsize.

    + +

    Clearly, logical splits based on input-size is insufficient for many + applications since record boundaries are to be respected. In such cases, the + application has to also implement a {@link RecordReader} on whom lies the + responsibilty to respect record-boundaries and present a record-oriented + view of the logical InputSplit to the individual task. + + @see InputSplit + @see RecordReader + @see JobClient + @see FileInputFormat]]> + + + + + + + + + + InputSplit. + + @return the number of bytes in the input split. + @throws IOException]]> + + + + + + InputSplit is + located as an array of Strings. + @throws IOException]]> + + + + InputSplit represents the data to be processed by an + individual {@link Mapper}. + +

    Typically, it presents a byte-oriented view on the input and is the + responsibility of {@link RecordReader} of the job to process this and present + a record-oriented view. + + @see InputFormat + @see RecordReader]]> + + + + + + + + + + SplitLocationInfos describing how the split + data is stored at each location. A null value indicates that all the + locations have the data stored on disk. + @throws IOException]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + JobClient.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + jobid doesn't correspond to any known job. + @throws IOException]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + JobClient is the primary interface for the user-job to interact + with the cluster. + + JobClient provides facilities to submit jobs, track their + progress, access component-tasks' reports/logs, get the Map-Reduce cluster + status information etc. + +

    The job submission process involves: +

      +
    1. + Checking the input and output specifications of the job. +
    2. +
    3. + Computing the {@link InputSplit}s for the job. +
    4. +
    5. + Setup the requisite accounting information for the {@link DistributedCache} + of the job, if necessary. +
    6. +
    7. + Copying the job's jar and configuration to the map-reduce system directory + on the distributed file-system. +
    8. +
    9. + Submitting the job to the cluster and optionally monitoring + it's status. +
    10. +
    + + Normally the user creates the application, describes various facets of the + job via {@link JobConf} and then uses the JobClient to submit + the job and monitor its progress. + +

    Here is an example on how to use JobClient:

    +

    +     // Create a new JobConf
    +     JobConf job = new JobConf(new Configuration(), MyJob.class);
    +     
    +     // Specify various job-specific parameters     
    +     job.setJobName("myjob");
    +     
    +     job.setInputPath(new Path("in"));
    +     job.setOutputPath(new Path("out"));
    +     
    +     job.setMapperClass(MyJob.MyMapper.class);
    +     job.setReducerClass(MyJob.MyReducer.class);
    +
    +     // Submit the job, then poll for progress until the job is complete
    +     JobClient.runJob(job);
    + 
    + + Job Control + +

    At times clients would chain map-reduce jobs to accomplish complex tasks + which cannot be done via a single map-reduce job. This is fairly easy since + the output of the job, typically, goes to distributed file-system and that + can be used as the input for the next job.

    + +

    However, this also means that the onus on ensuring jobs are complete + (success/failure) lies squarely on the clients. In such situations the + various job-control options are: +

      +
    1. + {@link #runJob(JobConf)} : submits the job and returns only after + the job has completed. +
    2. +
    3. + {@link #submitJob(JobConf)} : only submits the job, then poll the + returned handle to the {@link RunningJob} to query status and make + scheduling decisions. +
    4. +
    5. + {@link JobConf#setJobEndNotificationURI(String)} : setup a notification + on job-completion, thus avoiding polling. +
    6. +
    + + @see JobConf + @see ClusterStatus + @see Tool + @see DistributedCache]]> +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + If the parameter {@code loadDefaults} is false, the new instance + will not load resources from the default files. + + @param loadDefaults specifies whether to load from the default files]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true if framework should keep the intermediate files + for failed tasks, false otherwise.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true if the outputs of the maps are to be compressed, + false otherwise.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This comparator should be provided if the equivalence rules for keys + for sorting the intermediates are different from those for grouping keys + before each call to + {@link Reducer#reduce(Object, java.util.Iterator, OutputCollector, Reporter)}.

    + +

    For key-value pairs (K1,V1) and (K2,V2), the values (V1, V2) are passed + in a single call to the reduce function if K1 and K2 compare as equal.

    + +

    Since {@link #setOutputKeyComparatorClass(Class)} can be used to control + how keys are sorted, this can be used in conjunction to simulate + secondary sort on values.

    + +

    Note: This is not a guarantee of the combiner sort being + stable in any sense. (In any case, with the order of available + map-outputs to the combiner being non-deterministic, it wouldn't make + that much sense.)

    + + @param theClass the comparator class to be used for grouping keys for the + combiner. It should implement RawComparator. + @see #setOutputKeyComparatorClass(Class)]]> +
    +
    + + + + This comparator should be provided if the equivalence rules for keys + for sorting the intermediates are different from those for grouping keys + before each call to + {@link Reducer#reduce(Object, java.util.Iterator, OutputCollector, Reporter)}.

    + +

    For key-value pairs (K1,V1) and (K2,V2), the values (V1, V2) are passed + in a single call to the reduce function if K1 and K2 compare as equal.

    + +

    Since {@link #setOutputKeyComparatorClass(Class)} can be used to control + how keys are sorted, this can be used in conjunction to simulate + secondary sort on values.

    + +

    Note: This is not a guarantee of the reduce sort being + stable in any sense. (In any case, with the order of available + map-outputs to the reduce being non-deterministic, it wouldn't make + that much sense.)

    + + @param theClass the comparator class to be used for grouping keys. + It should implement RawComparator. + @see #setOutputKeyComparatorClass(Class) + @see #setCombinerKeyGroupingComparator(Class)]]> +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + combiner class used to combine map-outputs + before being sent to the reducers. Typically the combiner is same as the + the {@link Reducer} for the job i.e. {@link #getReducerClass()}. + + @return the user-defined combiner class used to combine map-outputs.]]> + + + + + + combiner class used to combine map-outputs + before being sent to the reducers. + +

    The combiner is an application-specified aggregation operation, which + can help cut down the amount of data transferred between the + {@link Mapper} and the {@link Reducer}, leading to better performance.

    + +

    The framework may invoke the combiner 0, 1, or multiple times, in both + the mapper and reducer tasks. In general, the combiner is called as the + sort/merge result is written to disk. The combiner must: +

      +
    • be side-effect free
    • +
    • have the same input and output key types and the same input and + output value types
    • +
    + +

    Typically the combiner is same as the Reducer for the + job i.e. {@link #setReducerClass(Class)}.

    + + @param theClass the user-defined combiner class used to combine + map-outputs.]]> +
    +
    + + + true. + + @return true if speculative execution be used for this job, + false otherwise.]]> + + + + + + true if speculative execution + should be turned on, else false.]]> + + + + + true. + + @return true if speculative execution be + used for this job for map tasks, + false otherwise.]]> + + + + + + true if speculative execution + should be turned on for map tasks, + else false.]]> + + + + + true. + + @return true if speculative execution be used + for reduce tasks for this job, + false otherwise.]]> + + + + + + true if speculative execution + should be turned on for reduce tasks, + else false.]]> + + + + + 1. + + @return the number of reduce tasks for this job.]]> + + + + + + Note: This is only a hint to the framework. The actual + number of spawned map tasks depends on the number of {@link InputSplit}s + generated by the job's {@link InputFormat#getSplits(JobConf, int)}. + + A custom {@link InputFormat} is typically used to accurately control + the number of map tasks for the job.

    + + How many maps? + +

    The number of maps is usually driven by the total size of the inputs + i.e. total number of blocks of the input files.

    + +

    The right level of parallelism for maps seems to be around 10-100 maps + per-node, although it has been set up to 300 or so for very cpu-light map + tasks. Task setup takes awhile, so it is best if the maps take at least a + minute to execute.

    + +

    The default behavior of file-based {@link InputFormat}s is to split the + input into logical {@link InputSplit}s based on the total size, in + bytes, of input files. However, the {@link FileSystem} blocksize of the + input files is treated as an upper bound for input splits. A lower bound + on the split size can be set via + + mapreduce.input.fileinputformat.split.minsize.

    + +

    Thus, if you expect 10TB of input data and have a blocksize of 128MB, + you'll end up with 82,000 maps, unless {@link #setNumMapTasks(int)} is + used to set it even higher.

    + + @param n the number of map tasks for this job. + @see InputFormat#getSplits(JobConf, int) + @see FileInputFormat + @see FileSystem#getDefaultBlockSize() + @see FileStatus#getBlockSize()]]> +
    +
    + + + 1. + + @return the number of reduce tasks for this job.]]> + + + + + + How many reduces? + +

    The right number of reduces seems to be 0.95 or + 1.75 multiplied by ( + available memory for reduce tasks + (The value of this should be smaller than + numNodes * yarn.nodemanager.resource.memory-mb + since the resource of memory is shared by map tasks and other + applications) / + + mapreduce.reduce.memory.mb). +

    + +

    With 0.95 all of the reduces can launch immediately and + start transfering map outputs as the maps finish. With 1.75 + the faster nodes will finish their first round of reduces and launch a + second wave of reduces doing a much better job of load balancing.

    + +

    Increasing the number of reduces increases the framework overhead, but + increases load balancing and lowers the cost of failures.

    + +

    The scaling factors above are slightly less than whole numbers to + reserve a few reduce slots in the framework for speculative-tasks, failures + etc.

    + + Reducer NONE + +

    It is legal to set the number of reduce-tasks to zero.

    + +

    In this case the output of the map-tasks directly go to distributed + file-system, to the path set by + {@link FileOutputFormat#setOutputPath(JobConf, Path)}. Also, the + framework doesn't sort the map-outputs before writing it out to HDFS.

    + + @param n the number of reduce tasks for this job.]]> +
    +
    + + + mapreduce.map.maxattempts + property. If this property is not already set, the default is 4 attempts. + + @return the max number of attempts per map task.]]> + + + + + + + + + + + mapreduce.reduce.maxattempts + property. If this property is not already set, the default is 4 attempts. + + @return the max number of attempts per reduce task.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + noFailures, the + tasktracker is blacklisted for this job. + + @param noFailures maximum no. of failures of a given job per tasktracker.]]> + + + + + blacklisted for this job. + + @return the maximum no. of failures of a given job per tasktracker.]]> + + + + + failed. + + Defaults to zero, i.e. any failed map-task results in + the job being declared as {@link JobStatus#FAILED}. + + @return the maximum percentage of map tasks that can fail without + the job being aborted.]]> + + + + + + failed. + + @param percent the maximum percentage of map tasks that can fail without + the job being aborted.]]> + + + + + failed. + + Defaults to zero, i.e. any failed reduce-task results + in the job being declared as {@link JobStatus#FAILED}. + + @return the maximum percentage of reduce tasks that can fail without + the job being aborted.]]> + + + + + + failed. + + @param percent the maximum percentage of reduce tasks that can fail without + the job being aborted.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The debug script can aid debugging of failed map tasks. The script is + given task's stdout, stderr, syslog, jobconf files as arguments.

    + +

    The debug command, run on the node where the map failed, is:

    +

    + $script $stdout $stderr $syslog $jobconf.
    + 
    + +

    The script file is distributed through {@link DistributedCache} + APIs. The script needs to be symlinked.

    + +

    Here is an example on how to submit a script +

    + job.setMapDebugScript("./myscript");
    + DistributedCache.createSymlink(job);
    + DistributedCache.addCacheFile("/debug/scripts/myscript#myscript");
    + 
    + + @param mDbgScript the script name]]> +
    +
    + + + + + + + + + The debug script can aid debugging of failed reduce tasks. The script + is given task's stdout, stderr, syslog, jobconf files as arguments.

    + +

    The debug command, run on the node where the map failed, is:

    +

    + $script $stdout $stderr $syslog $jobconf.
    + 
    + +

    The script file is distributed through {@link DistributedCache} + APIs. The script file needs to be symlinked

    + +

    Here is an example on how to submit a script +

    + job.setReduceDebugScript("./myscript");
    + DistributedCache.createSymlink(job);
    + DistributedCache.addCacheFile("/debug/scripts/myscript#myscript");
    + 
    + + @param rDbgScript the script name]]> +
    +
    + + + + + + + + null if it hasn't + been set. + @see #setJobEndNotificationURI(String)]]> + + + + + + The uri can contain 2 special parameters: $jobId and + $jobStatus. Those, if present, are replaced by the job's + identifier and completion-status respectively.

    + +

    This is typically used by application-writers to implement chaining of + Map-Reduce jobs in an asynchronous manner.

    + + @param uri the job end notification uri + @see JobStatus]]> +
    +
    + + + + When a job starts, a shared directory is created at location + + ${mapreduce.cluster.local.dir}/taskTracker/$user/jobcache/$jobid/work/ . + This directory is exposed to the users through + mapreduce.job.local.dir . + So, the tasks can use this space + as scratch space and share files among them.

    + This value is available as System property also. + + @return The localized job specific shared directory]]> +
    +
    + + + + For backward compatibility, if the job configuration sets the + key {@link #MAPRED_TASK_MAXVMEM_PROPERTY} to a value different + from {@link #DISABLED_MEMORY_LIMIT}, that value will be used + after converting it from bytes to MB. + @return memory required to run a map task of the job, in MB,]]> + + + + + + + + + For backward compatibility, if the job configuration sets the + key {@link #MAPRED_TASK_MAXVMEM_PROPERTY} to a value different + from {@link #DISABLED_MEMORY_LIMIT}, that value will be used + after converting it from bytes to MB. + @return memory required to run a reduce task of the job, in MB.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This method is deprecated. Now, different memory limits can be + set for map and reduce tasks of a job, in MB. +

    + For backward compatibility, if the job configuration sets the + key {@link #MAPRED_TASK_MAXVMEM_PROPERTY}, that value is returned. + Otherwise, this method will return the larger of the values returned by + {@link #getMemoryForMapTask()} and {@link #getMemoryForReduceTask()} + after converting them into bytes. + + @return Memory required to run a task of this job, in bytes. + @see #setMaxVirtualMemoryForTask(long) + @deprecated Use {@link #getMemoryForMapTask()} and + {@link #getMemoryForReduceTask()}]]> + + + + + + + mapred.task.maxvmem is split into + mapreduce.map.memory.mb + and mapreduce.map.memory.mb,mapred + each of the new key are set + as mapred.task.maxvmem / 1024 + as new values are in MB + + @param vmem Maximum amount of virtual memory in bytes any task of this job + can use. + @see #getMaxVirtualMemoryForTask() + @deprecated + Use {@link #setMemoryForMapTask(long mem)} and + Use {@link #setMemoryForReduceTask(long mem)}]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + k1=v1,k2=v2. Further it can + reference existing environment variables via $key on + Linux or %key% on Windows. + + Example: +

      +
    • A=foo - This will set the env variable A to foo.
    • +
    • B=$X:c This is inherit tasktracker's X env variable on Linux.
    • +
    • B=%X%;c This is inherit tasktracker's X env variable on Windows.
    • +
    + + @deprecated Use {@link #MAPRED_MAP_TASK_ENV} or + {@link #MAPRED_REDUCE_TASK_ENV}]]> +
    + + + + k1=v1,k2=v2. Further it can + reference existing environment variables via $key on + Linux or %key% on Windows. + + Example: +
      +
    • A=foo - This will set the env variable A to foo.
    • +
    • B=$X:c This is inherit tasktracker's X env variable on Linux.
    • +
    • B=%X%;c This is inherit tasktracker's X env variable on Windows.
    • +
    ]]> +
    +
    + + + k1=v1,k2=v2. Further it can + reference existing environment variables via $key on + Linux or %key% on Windows. + + Example: +
      +
    • A=foo - This will set the env variable A to foo.
    • +
    • B=$X:c This is inherit tasktracker's X env variable on Linux.
    • +
    • B=%X%;c This is inherit tasktracker's X env variable on Windows.
    • +
    ]]> +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + JobConf is the primary interface for a user to describe a + map-reduce job to the Hadoop framework for execution. The framework tries to + faithfully execute the job as-is described by JobConf, however: +
      +
    1. + Some configuration parameters might have been marked as + + final by administrators and hence cannot be altered. +
    2. +
    3. + While some job parameters are straight-forward to set + (e.g. {@link #setNumReduceTasks(int)}), some parameters interact subtly + with the rest of the framework and/or job-configuration and is relatively + more complex for the user to control finely + (e.g. {@link #setNumMapTasks(int)}). +
    4. +
    + +

    JobConf typically specifies the {@link Mapper}, combiner + (if any), {@link Partitioner}, {@link Reducer}, {@link InputFormat} and + {@link OutputFormat} implementations to be used etc. + +

    Optionally JobConf is used to specify other advanced facets + of the job such as Comparators to be used, files to be put in + the {@link DistributedCache}, whether or not intermediate and/or job outputs + are to be compressed (and how), debugability via user-provided scripts + ( {@link #setMapDebugScript(String)}/{@link #setReduceDebugScript(String)}), + for doing post-processing on task logs, task's stdout, stderr, syslog. + and etc.

    + +

    Here is an example on how to configure a job via JobConf:

    +

    +     // Create a new JobConf
    +     JobConf job = new JobConf(new Configuration(), MyJob.class);
    +     
    +     // Specify various job-specific parameters     
    +     job.setJobName("myjob");
    +     
    +     FileInputFormat.setInputPaths(job, new Path("in"));
    +     FileOutputFormat.setOutputPath(job, new Path("out"));
    +     
    +     job.setMapperClass(MyJob.MyMapper.class);
    +     job.setCombinerClass(MyJob.MyReducer.class);
    +     job.setReducerClass(MyJob.MyReducer.class);
    +     
    +     job.setInputFormat(SequenceFileInputFormat.class);
    +     job.setOutputFormat(SequenceFileOutputFormat.class);
    + 
    + + @see JobClient + @see ClusterStatus + @see Tool + @see DistributedCache]]> +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + any job + run on the jobtracker started at 200707121733, we would use : +
     
    + JobID.getTaskIDsPattern("200707121733", null);
    + 
    + which will return : +
     "job_200707121733_[0-9]*" 
    + @param jtIdentifier jobTracker identifier, or null + @param jobId job number, or null + @return a regex pattern matching JobIDs]]> +
    +
    + + + An example JobID is : + job_200707121733_0003 , which represents the third job + running at the jobtracker started at 200707121733. +

    + Applications should never construct or parse JobID strings, but rather + use appropriate constructors or {@link #forName(String)} method. + + @see TaskID + @see TaskAttemptID]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Output pairs need not be of the same types as input pairs. A given + input pair may map to zero or many output pairs. Output pairs are + collected with calls to + {@link OutputCollector#collect(Object,Object)}.

    + +

    Applications can use the {@link Reporter} provided to report progress + or just indicate that they are alive. In scenarios where the application + takes significant amount of time to process individual key/value + pairs, this is crucial since the framework might assume that the task has + timed-out and kill that task. The other way of avoiding this is to set + + mapreduce.task.timeout to a high-enough value (or even zero for no + time-outs).

    + + @param key the input key. + @param value the input value. + @param output collects mapped keys and values. + @param reporter facility to report progress.]]> +
    + + + Maps are the individual tasks which transform input records into a + intermediate records. The transformed intermediate records need not be of + the same type as the input records. A given input pair may map to zero or + many output pairs.

    + +

    The Hadoop Map-Reduce framework spawns one map task for each + {@link InputSplit} generated by the {@link InputFormat} for the job. + Mapper implementations can access the {@link JobConf} for the + job via the {@link JobConfigurable#configure(JobConf)} and initialize + themselves. Similarly they can use the {@link Closeable#close()} method for + de-initialization.

    + +

    The framework then calls + {@link #map(Object, Object, OutputCollector, Reporter)} + for each key/value pair in the InputSplit for that task.

    + +

    All intermediate values associated with a given output key are + subsequently grouped by the framework, and passed to a {@link Reducer} to + determine the final output. Users can control the grouping by specifying + a Comparator via + {@link JobConf#setOutputKeyComparatorClass(Class)}.

    + +

    The grouped Mapper outputs are partitioned per + Reducer. Users can control which keys (and hence records) go to + which Reducer by implementing a custom {@link Partitioner}. + +

    Users can optionally specify a combiner, via + {@link JobConf#setCombinerClass(Class)}, to perform local aggregation of the + intermediate outputs, which helps to cut down the amount of data transferred + from the Mapper to the Reducer. + +

    The intermediate, grouped outputs are always stored in + {@link SequenceFile}s. Applications can specify if and how the intermediate + outputs are to be compressed and which {@link CompressionCodec}s are to be + used via the JobConf.

    + +

    If the job has + zero + reduces then the output of the Mapper is directly written + to the {@link FileSystem} without grouping by keys.

    + +

    Example:

    +

    +     public class MyMapper<K extends WritableComparable, V extends Writable> 
    +     extends MapReduceBase implements Mapper<K, V, K, V> {
    +     
    +       static enum MyCounters { NUM_RECORDS }
    +       
    +       private String mapTaskId;
    +       private String inputFile;
    +       private int noRecords = 0;
    +       
    +       public void configure(JobConf job) {
    +         mapTaskId = job.get(JobContext.TASK_ATTEMPT_ID);
    +         inputFile = job.get(JobContext.MAP_INPUT_FILE);
    +       }
    +       
    +       public void map(K key, V val,
    +                       OutputCollector<K, V> output, Reporter reporter)
    +       throws IOException {
    +         // Process the <key, value> pair (assume this takes a while)
    +         // ...
    +         // ...
    +         
    +         // Let the framework know that we are alive, and kicking!
    +         // reporter.progress();
    +         
    +         // Process some more
    +         // ...
    +         // ...
    +         
    +         // Increment the no. of <key, value> pairs processed
    +         ++noRecords;
    +
    +         // Increment counters
    +         reporter.incrCounter(NUM_RECORDS, 1);
    +        
    +         // Every 100 records update application-level status
    +         if ((noRecords%100) == 0) {
    +           reporter.setStatus(mapTaskId + " processed " + noRecords + 
    +                              " from input-file: " + inputFile); 
    +         }
    +         
    +         // Output the result
    +         output.collect(key, val);
    +       }
    +     }
    + 
    + +

    Applications may write a custom {@link MapRunnable} to exert greater + control on map processing e.g. multi-threaded Mappers etc.

    + + @see JobConf + @see InputFormat + @see Partitioner + @see Reducer + @see MapReduceBase + @see MapRunnable + @see SequenceFile]]> +
    +
    + + + + + + + + + + + + + + + + + + + + + Provides default no-op implementations for a few methods, most non-trivial + applications need to override some of them.

    ]]> +
    +
    + + + + + + + + + + + <key, value> pairs. + +

    Mapping of input records to output records is complete when this method + returns.

    + + @param input the {@link RecordReader} to read the input records. + @param output the {@link OutputCollector} to collect the outputrecords. + @param reporter {@link Reporter} to report progress, status-updates etc. + @throws IOException]]> +
    +
    + + Custom implementations of MapRunnable can exert greater + control on map processing e.g. multi-threaded, asynchronous mappers etc.

    + + @see Mapper]]> +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + nearly + equal content length.
    + Subclasses implement {@link #getRecordReader(InputSplit, JobConf, Reporter)} + to construct RecordReader's for MultiFileSplit's. + @see MultiFileSplit]]> +
    +
    + + + + + + + + + + + + + MultiFileSplit can be used to implement {@link RecordReader}'s, with + reading one record per file. + @see FileSplit + @see MultiFileInputFormat]]> + + + + + + + + + + + + + + + <key, value> pairs output by {@link Mapper}s + and {@link Reducer}s. + +

    OutputCollector is the generalization of the facility + provided by the Map-Reduce framework to collect data output by either the + Mapper or the Reducer i.e. intermediate outputs + or the output of the job.

    ]]> +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true if task output recovery is supported, + false otherwise + @throws IOException + @see #recoverTask(TaskAttemptContext)]]> + + + + + + + true repeatable job commit is supported, + false otherwise + @throws IOException]]> + + + + + + + + + + + OutputCommitter. This is called from the application master + process, but it is called individually for each task. + + If an exception is thrown the task will be attempted again. + + @param taskContext Context of the task whose output is being recovered + @throws IOException]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + OutputCommitter describes the commit of task output for a + Map-Reduce job. + +

    The Map-Reduce framework relies on the OutputCommitter of + the job to:

    +

      +
    1. + Setup the job during initialization. For example, create the temporary + output directory for the job during the initialization of the job. +
    2. +
    3. + Cleanup the job after the job completion. For example, remove the + temporary output directory after the job completion. +
    4. +
    5. + Setup the task temporary output. +
    6. +
    7. + Check whether a task needs a commit. This is to avoid the commit + procedure if a task does not need commit. +
    8. +
    9. + Commit of the task output. +
    10. +
    11. + Discard the task commit. +
    12. +
    + The methods in this class can be called from several different processes and + from several different contexts. It is important to know which process and + which context each is called from. Each method should be marked accordingly + in its documentation. It is also important to note that not all methods are + guaranteed to be called once and only once. If a method is not guaranteed to + have this property the output committer needs to handle this appropriately. + Also note it will only be in rare situations where they may be called + multiple times for the same task. + + @see FileOutputCommitter + @see JobContext + @see TaskAttemptContext]]> +
    +
    + + + + + + + + + + + + + + + + + + + This is to validate the output specification for the job when it is + a job is submitted. Typically checks that it does not already exist, + throwing an exception when it already exists, so that output is not + overwritten.

    + + @param ignored + @param job job configuration. + @throws IOException when output should not be attempted]]> +
    +
    + + OutputFormat describes the output-specification for a + Map-Reduce job. + +

    The Map-Reduce framework relies on the OutputFormat of the + job to:

    +

      +
    1. + Validate the output-specification of the job. For e.g. check that the + output directory doesn't already exist. +
    2. + Provide the {@link RecordWriter} implementation to be used to write out + the output files of the job. Output files are stored in a + {@link FileSystem}. +
    3. +
    + + @see RecordWriter + @see JobConf]]> +
    +
    + + + + + + + + + + + + + + + + + + + + + + + Typically a hash function on a all or a subset of the key.

    + + @param key the key to be paritioned. + @param value the entry value. + @param numPartitions the total number of partitions. + @return the partition number for the key.]]> +
    +
    + + Partitioner controls the partitioning of the keys of the + intermediate map-outputs. The key (or a subset of the key) is used to derive + the partition, typically by a hash function. The total number of partitions + is the same as the number of reduce tasks for the job. Hence this controls + which of the m reduce tasks the intermediate key (and hence the + record) is sent for reduction.

    + + @see Reducer]]> +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0.0 to 1.0. + @throws IOException]]> + + + + RecordReader reads <key, value> pairs from an + {@link InputSplit}. + +

    RecordReader, typically, converts the byte-oriented view of + the input, provided by the InputSplit, and presents a + record-oriented view for the {@link Mapper} and {@link Reducer} tasks for + processing. It thus assumes the responsibility of processing record + boundaries and presenting the tasks with keys and values.

    + + @see InputSplit + @see InputFormat]]> +
    +
    + + + + + + + + + + + + + + + + RecordWriter to future operations. + + @param reporter facility to report progress. + @throws IOException]]> + + + + RecordWriter writes the output <key, value> pairs + to an output file. + +

    RecordWriter implementations write the job outputs to the + {@link FileSystem}. + + @see OutputFormat]]> + + + + + + + + + + + + + + + Reduces values for a given key. + +

    The framework calls this method for each + <key, (list of values)> pair in the grouped inputs. + Output values must be of the same type as input values. Input keys must + not be altered. The framework will reuse the key and value objects + that are passed into the reduce, therefore the application should clone + the objects they want to keep a copy of. In many cases, all values are + combined into zero or one value. +

    + +

    Output pairs are collected with calls to + {@link OutputCollector#collect(Object,Object)}.

    + +

    Applications can use the {@link Reporter} provided to report progress + or just indicate that they are alive. In scenarios where the application + takes a significant amount of time to process individual key/value + pairs, this is crucial since the framework might assume that the task has + timed-out and kill that task. The other way of avoiding this is to set + + mapreduce.task.timeout to a high-enough value (or even zero for no + time-outs).

    + + @param key the key. + @param values the list of values to reduce. + @param output to collect keys and combined values. + @param reporter facility to report progress.]]> +
    + + + The number of Reducers for the job is set by the user via + {@link JobConf#setNumReduceTasks(int)}. Reducer implementations + can access the {@link JobConf} for the job via the + {@link JobConfigurable#configure(JobConf)} method and initialize themselves. + Similarly they can use the {@link Closeable#close()} method for + de-initialization.

    + +

    Reducer has 3 primary phases:

    +
      +
    1. + + Shuffle + +

      Reducer is input the grouped output of a {@link Mapper}. + In the phase the framework, for each Reducer, fetches the + relevant partition of the output of all the Mappers, via HTTP. +

      +
    2. + +
    3. + Sort + +

      The framework groups Reducer inputs by keys + (since different Mappers may have output the same key) in this + stage.

      + +

      The shuffle and sort phases occur simultaneously i.e. while outputs are + being fetched they are merged.

      + + SecondarySort + +

      If equivalence rules for keys while grouping the intermediates are + different from those for grouping keys before reduction, then one may + specify a Comparator via + {@link JobConf#setOutputValueGroupingComparator(Class)}.Since + {@link JobConf#setOutputKeyComparatorClass(Class)} can be used to + control how intermediate keys are grouped, these can be used in conjunction + to simulate secondary sort on values.

      + + + For example, say that you want to find duplicate web pages and tag them + all with the url of the "best" known example. You would set up the job + like: +
        +
      • Map Input Key: url
      • +
      • Map Input Value: document
      • +
      • Map Output Key: document checksum, url pagerank
      • +
      • Map Output Value: url
      • +
      • Partitioner: by checksum
      • +
      • OutputKeyComparator: by checksum and then decreasing pagerank
      • +
      • OutputValueGroupingComparator: by checksum
      • +
      +
    4. + +
    5. + Reduce + +

      In this phase the + {@link #reduce(Object, Iterator, OutputCollector, Reporter)} + method is called for each <key, (list of values)> pair in + the grouped inputs.

      +

      The output of the reduce task is typically written to the + {@link FileSystem} via + {@link OutputCollector#collect(Object, Object)}.

      +
    6. +
    + +

    The output of the Reducer is not re-sorted.

    + +

    Example:

    +

    +     public class MyReducer<K extends WritableComparable, V extends Writable> 
    +     extends MapReduceBase implements Reducer<K, V, K, V> {
    +     
    +       static enum MyCounters { NUM_RECORDS }
    +        
    +       private String reduceTaskId;
    +       private int noKeys = 0;
    +       
    +       public void configure(JobConf job) {
    +         reduceTaskId = job.get(JobContext.TASK_ATTEMPT_ID);
    +       }
    +       
    +       public void reduce(K key, Iterator<V> values,
    +                          OutputCollector<K, V> output, 
    +                          Reporter reporter)
    +       throws IOException {
    +       
    +         // Process
    +         int noValues = 0;
    +         while (values.hasNext()) {
    +           V value = values.next();
    +           
    +           // Increment the no. of values for this key
    +           ++noValues;
    +           
    +           // Process the <key, value> pair (assume this takes a while)
    +           // ...
    +           // ...
    +           
    +           // Let the framework know that we are alive, and kicking!
    +           if ((noValues%10) == 0) {
    +             reporter.progress();
    +           }
    +         
    +           // Process some more
    +           // ...
    +           // ...
    +           
    +           // Output the <key, value> 
    +           output.collect(key, value);
    +         }
    +         
    +         // Increment the no. of <key, list of values> pairs processed
    +         ++noKeys;
    +         
    +         // Increment counters
    +         reporter.incrCounter(NUM_RECORDS, 1);
    +         
    +         // Every 100 keys update application-level status
    +         if ((noKeys%100) == 0) {
    +           reporter.setStatus(reduceTaskId + " processed " + noKeys);
    +         }
    +       }
    +     }
    + 
    + + @see Mapper + @see Partitioner + @see Reporter + @see MapReduceBase]]> +
    +
    + + + + + + + + + + + + + + Counter of the given group/name.]]> + + + + + + + Counter of the given group/name.]]> + + + + + + + Enum. + @param amount A non-negative amount by which the counter is to + be incremented.]]> + + + + + + + + + + + + + + InputSplit that the map is reading from. + @throws UnsupportedOperationException if called outside a mapper]]> + + + + + + + + + + + + + + {@link Mapper} and {@link Reducer} can use the Reporter + provided to report progress or just indicate that they are alive. In + scenarios where the application takes significant amount of time to + process individual key/value pairs, this is crucial since the framework + might assume that the task has timed-out and kill that task. + +

    Applications can also update {@link Counters} via the provided + Reporter .

    + + @see Progressable + @see Counters]]> +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + progress of the job's map-tasks, as a float between 0.0 + and 1.0. When all map tasks have completed, the function returns 1.0. + + @return the progress of the job's map-tasks. + @throws IOException]]> + + + + + + progress of the job's reduce-tasks, as a float between 0.0 + and 1.0. When all reduce tasks have completed, the function returns 1.0. + + @return the progress of the job's reduce-tasks. + @throws IOException]]> + + + + + + progress of the job's cleanup-tasks, as a float between 0.0 + and 1.0. When all cleanup tasks have completed, the function returns 1.0. + + @return the progress of the job's cleanup-tasks. + @throws IOException]]> + + + + + + progress of the job's setup-tasks, as a float between 0.0 + and 1.0. When all setup tasks have completed, the function returns 1.0. + + @return the progress of the job's setup-tasks. + @throws IOException]]> + + + + + + true if the job is complete, else false. + @throws IOException]]> + + + + + + true if the job succeeded, else false. + @throws IOException]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true if the job retired, else false. + @throws IOException]]> + + + + + + + + + + RunningJob is the user-interface to query for details on a + running Map-Reduce job. + +

    Clients can get hold of RunningJob via the {@link JobClient} + and then query the running-job for details such as name, configuration, + progress etc.

    + + @see JobClient]]> +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + This allows the user to specify the key class to be different + from the actual class ({@link BytesWritable}) used for writing

    + + @param conf the {@link JobConf} to modify + @param theClass the SequenceFile output key class.]]> +
    +
    + + + + + This allows the user to specify the value class to be different + from the actual class ({@link BytesWritable}) used for writing

    + + @param conf the {@link JobConf} to modify + @param theClass the SequenceFile output key class.]]> +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true if auto increment + {@link SkipBadRecords#COUNTER_MAP_PROCESSED_RECORDS}. + false otherwise.]]> + + + + + + + + + + + + + true if auto increment + {@link SkipBadRecords#COUNTER_REDUCE_PROCESSED_GROUPS}. + false otherwise.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Hadoop provides an optional mode of execution in which the bad records + are detected and skipped in further attempts. + +

    This feature can be used when map/reduce tasks crashes deterministically on + certain input. This happens due to bugs in the map/reduce function. The usual + course would be to fix these bugs. But sometimes this is not possible; + perhaps the bug is in third party libraries for which the source code is + not available. Due to this, the task never reaches to completion even with + multiple attempts and complete data for that task is lost.

    + +

    With this feature, only a small portion of data is lost surrounding + the bad record, which may be acceptable for some user applications. + see {@link SkipBadRecords#setMapperMaxSkipRecords(Configuration, long)}

    + +

    The skipping mode gets kicked off after certain no of failures + see {@link SkipBadRecords#setAttemptsToStartSkipping(Configuration, int)}

    + +

    In the skipping mode, the map/reduce task maintains the record range which + is getting processed at all times. Before giving the input to the + map/reduce function, it sends this record range to the Task tracker. + If task crashes, the Task tracker knows which one was the last reported + range. On further attempts that range get skipped.

    ]]> +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + all task attempt IDs + of any jobtracker, in any job, of the first + map task, we would use : +
     
    + TaskAttemptID.getTaskAttemptIDsPattern(null, null, true, 1, null);
    + 
    + which will return : +
     "attempt_[^_]*_[0-9]*_m_000001_[0-9]*" 
    + @param jtIdentifier jobTracker identifier, or null + @param jobId job number, or null + @param isMap whether the tip is a map, or null + @param taskId taskId number, or null + @param attemptId the task attempt number, or null + @return a regex pattern matching TaskAttemptIDs]]> +
    +
    + + + + + + + + all task attempt IDs + of any jobtracker, in any job, of the first + map task, we would use : +
     
    + TaskAttemptID.getTaskAttemptIDsPattern(null, null, TaskType.MAP, 1, null);
    + 
    + which will return : +
     "attempt_[^_]*_[0-9]*_m_000001_[0-9]*" 
    + @param jtIdentifier jobTracker identifier, or null + @param jobId job number, or null + @param type the {@link TaskType} + @param taskId taskId number, or null + @param attemptId the task attempt number, or null + @return a regex pattern matching TaskAttemptIDs]]> +
    +
    + + + An example TaskAttemptID is : + attempt_200707121733_0003_m_000005_0 , which represents the + zeroth task attempt for the fifth map task in the third job + running at the jobtracker started at 200707121733. +

    + Applications should never construct or parse TaskAttemptID strings + , but rather use appropriate constructors or {@link #forName(String)} + method. + + @see JobID + @see TaskID]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + the first map task + of any jobtracker, of any job, we would use : +

     
    + TaskID.getTaskIDsPattern(null, null, true, 1);
    + 
    + which will return : +
     "task_[^_]*_[0-9]*_m_000001*" 
    + @param jtIdentifier jobTracker identifier, or null + @param jobId job number, or null + @param isMap whether the tip is a map, or null + @param taskId taskId number, or null + @return a regex pattern matching TaskIDs + @deprecated Use {@link TaskID#getTaskIDsPattern(String, Integer, TaskType, + Integer)}]]> +
    + + + + + + + + the first map task + of any jobtracker, of any job, we would use : +
     
    + TaskID.getTaskIDsPattern(null, null, true, 1);
    + 
    + which will return : +
     "task_[^_]*_[0-9]*_m_000001*" 
    + @param jtIdentifier jobTracker identifier, or null + @param jobId job number, or null + @param type the {@link TaskType}, or null + @param taskId taskId number, or null + @return a regex pattern matching TaskIDs]]> +
    +
    + + + + + + + An example TaskID is : + task_200707121733_0003_m_000005 , which represents the + fifth map task in the third job running at the jobtracker + started at 200707121733. +

    + Applications should never construct or parse TaskID strings + , but rather use appropriate constructors or {@link #forName(String)} + method. + + @see JobID + @see TaskAttemptID]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true if the Job was added.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ([,]*) + func ::= tbl(,"") + class ::= @see java.lang.Class#forName(java.lang.String) + path ::= @see org.apache.hadoop.fs.Path#Path(java.lang.String) + } + Reads expression from the mapred.join.expr property and + user-supplied join types from mapred.join.define.<ident> + types. Paths supplied to tbl are given as input paths to the + InputFormat class listed. + @see #compose(java.lang.String, java.lang.Class, java.lang.String...)]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ,

    ) }]]> + + + + + + + + (tbl(,),tbl(,),...,tbl(,)) }]]> + + + + + + + + (tbl(,),tbl(,),...,tbl(,)) }]]> + + + + mapred.join.define.<ident> to a classname. In the expression + mapred.join.expr, the identifier will be assumed to be a + ComposableRecordReader. + mapred.join.keycomparator can be a classname used to compare keys + in the join. + @see #setFormat + @see JoinRecordReader + @see MultiFilterRecordReader]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ...... + }]]> + + + + + + + + + + + + + + + + + + + + + capacity children to position + id in the parent reader. + The id of a root CompositeRecordReader is -1 by convention, but relying + on this is not recommended.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + override(S1,S2,S3) will prefer values + from S3 over S2, and values from S2 over S1 for all keys + emitted from all sources.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + It has to be specified how key and values are passed from one element of + the chain to the next, by value or by reference. If a Mapper leverages the + assumed semantics that the key and values are not modified by the collector + 'by value' must be used. If the Mapper does not expect this semantics, as + an optimization to avoid serialization and deserialization 'by reference' + can be used. +

    + For the added Mapper the configuration given for it, + mapperConf, have precedence over the job's JobConf. This + precedence is in effect when the task is running. +

    + IMPORTANT: There is no need to specify the output key/value classes for the + ChainMapper, this is done by the addMapper for the last mapper in the chain +

    + + @param job job's JobConf to add the Mapper class. + @param klass the Mapper class to add. + @param inputKeyClass mapper input key class. + @param inputValueClass mapper input value class. + @param outputKeyClass mapper output key class. + @param outputValueClass mapper output value class. + @param byValue indicates if key/values should be passed by value + to the next Mapper in the chain, if any. + @param mapperConf a JobConf with the configuration for the Mapper + class. It is recommended to use a JobConf without default values using the + JobConf(boolean loadDefaults) constructor with FALSE.]]> + + + + + + + If this method is overriden super.configure(...) should be + invoked at the beginning of the overwriter method.]]> + + + + + + + + + + map(...) methods of the Mappers in the chain.]]> + + + + + + + If this method is overriden super.close() should be + invoked at the end of the overwriter method.]]> + + + + + The Mapper classes are invoked in a chained (or piped) fashion, the output of + the first becomes the input of the second, and so on until the last Mapper, + the output of the last Mapper will be written to the task's output. +

    + The key functionality of this feature is that the Mappers in the chain do not + need to be aware that they are executed in a chain. This enables having + reusable specialized Mappers that can be combined to perform composite + operations within a single task. +

    + Special care has to be taken when creating chains that the key/values output + by a Mapper are valid for the following Mapper in the chain. It is assumed + all Mappers and the Reduce in the chain use maching output and input key and + value classes as no conversion is done by the chaining code. +

    + Using the ChainMapper and the ChainReducer classes is possible to compose + Map/Reduce jobs that look like [MAP+ / REDUCE MAP*]. And + immediate benefit of this pattern is a dramatic reduction in disk IO. +

    + IMPORTANT: There is no need to specify the output key/value classes for the + ChainMapper, this is done by the addMapper for the last mapper in the chain. +

    + ChainMapper usage pattern: +

    +

    + ...
    + conf.setJobName("chain");
    + conf.setInputFormat(TextInputFormat.class);
    + conf.setOutputFormat(TextOutputFormat.class);
    +
    + JobConf mapAConf = new JobConf(false);
    + ...
    + ChainMapper.addMapper(conf, AMap.class, LongWritable.class, Text.class,
    +   Text.class, Text.class, true, mapAConf);
    +
    + JobConf mapBConf = new JobConf(false);
    + ...
    + ChainMapper.addMapper(conf, BMap.class, Text.class, Text.class,
    +   LongWritable.class, Text.class, false, mapBConf);
    +
    + JobConf reduceConf = new JobConf(false);
    + ...
    + ChainReducer.setReducer(conf, XReduce.class, LongWritable.class, Text.class,
    +   Text.class, Text.class, true, reduceConf);
    +
    + ChainReducer.addMapper(conf, CMap.class, Text.class, Text.class,
    +   LongWritable.class, Text.class, false, null);
    +
    + ChainReducer.addMapper(conf, DMap.class, LongWritable.class, Text.class,
    +   LongWritable.class, LongWritable.class, true, null);
    +
    + FileInputFormat.setInputPaths(conf, inDir);
    + FileOutputFormat.setOutputPath(conf, outDir);
    + ...
    +
    + JobClient jc = new JobClient(conf);
    + RunningJob job = jc.submitJob(conf);
    + ...
    + 
    ]]> +
    +
    + + + + + + + + + + + + + + + + + + + + + It has to be specified how key and values are passed from one element of + the chain to the next, by value or by reference. If a Reducer leverages the + assumed semantics that the key and values are not modified by the collector + 'by value' must be used. If the Reducer does not expect this semantics, as + an optimization to avoid serialization and deserialization 'by reference' + can be used. +

    + For the added Reducer the configuration given for it, + reducerConf, have precedence over the job's JobConf. This + precedence is in effect when the task is running. +

    + IMPORTANT: There is no need to specify the output key/value classes for the + ChainReducer, this is done by the setReducer or the addMapper for the last + element in the chain. + + @param job job's JobConf to add the Reducer class. + @param klass the Reducer class to add. + @param inputKeyClass reducer input key class. + @param inputValueClass reducer input value class. + @param outputKeyClass reducer output key class. + @param outputValueClass reducer output value class. + @param byValue indicates if key/values should be passed by value + to the next Mapper in the chain, if any. + @param reducerConf a JobConf with the configuration for the Reducer + class. It is recommended to use a JobConf without default values using the + JobConf(boolean loadDefaults) constructor with FALSE.]]> + + + + + + + + + + + + + + It has to be specified how key and values are passed from one element of + the chain to the next, by value or by reference. If a Mapper leverages the + assumed semantics that the key and values are not modified by the collector + 'by value' must be used. If the Mapper does not expect this semantics, as + an optimization to avoid serialization and deserialization 'by reference' + can be used. +

    + For the added Mapper the configuration given for it, + mapperConf, have precedence over the job's JobConf. This + precedence is in effect when the task is running. +

    + IMPORTANT: There is no need to specify the output key/value classes for the + ChainMapper, this is done by the addMapper for the last mapper in the chain + . + + @param job chain job's JobConf to add the Mapper class. + @param klass the Mapper class to add. + @param inputKeyClass mapper input key class. + @param inputValueClass mapper input value class. + @param outputKeyClass mapper output key class. + @param outputValueClass mapper output value class. + @param byValue indicates if key/values should be passed by value + to the next Mapper in the chain, if any. + @param mapperConf a JobConf with the configuration for the Mapper + class. It is recommended to use a JobConf without default values using the + JobConf(boolean loadDefaults) constructor with FALSE.]]> + + + + + + + If this method is overriden super.configure(...) should be + invoked at the beginning of the overwriter method.]]> + + + + + + + + + + reduce(...) method of the Reducer with the + map(...) methods of the Mappers in the chain.]]> + + + + + + + If this method is overriden super.close() should be + invoked at the end of the overwriter method.]]> + + + + + For each record output by the Reducer, the Mapper classes are invoked in a + chained (or piped) fashion, the output of the first becomes the input of the + second, and so on until the last Mapper, the output of the last Mapper will + be written to the task's output. +

    + The key functionality of this feature is that the Mappers in the chain do not + need to be aware that they are executed after the Reducer or in a chain. + This enables having reusable specialized Mappers that can be combined to + perform composite operations within a single task. +

    + Special care has to be taken when creating chains that the key/values output + by a Mapper are valid for the following Mapper in the chain. It is assumed + all Mappers and the Reduce in the chain use maching output and input key and + value classes as no conversion is done by the chaining code. +

    + Using the ChainMapper and the ChainReducer classes is possible to compose + Map/Reduce jobs that look like [MAP+ / REDUCE MAP*]. And + immediate benefit of this pattern is a dramatic reduction in disk IO. +

    + IMPORTANT: There is no need to specify the output key/value classes for the + ChainReducer, this is done by the setReducer or the addMapper for the last + element in the chain. +

    + ChainReducer usage pattern: +

    +

    + ...
    + conf.setJobName("chain");
    + conf.setInputFormat(TextInputFormat.class);
    + conf.setOutputFormat(TextOutputFormat.class);
    +
    + JobConf mapAConf = new JobConf(false);
    + ...
    + ChainMapper.addMapper(conf, AMap.class, LongWritable.class, Text.class,
    +   Text.class, Text.class, true, mapAConf);
    +
    + JobConf mapBConf = new JobConf(false);
    + ...
    + ChainMapper.addMapper(conf, BMap.class, Text.class, Text.class,
    +   LongWritable.class, Text.class, false, mapBConf);
    +
    + JobConf reduceConf = new JobConf(false);
    + ...
    + ChainReducer.setReducer(conf, XReduce.class, LongWritable.class, Text.class,
    +   Text.class, Text.class, true, reduceConf);
    +
    + ChainReducer.addMapper(conf, CMap.class, Text.class, Text.class,
    +   LongWritable.class, Text.class, false, null);
    +
    + ChainReducer.addMapper(conf, DMap.class, LongWritable.class, Text.class,
    +   LongWritable.class, LongWritable.class, true, null);
    +
    + FileInputFormat.setInputPaths(conf, inDir);
    + FileOutputFormat.setOutputPath(conf, outDir);
    + ...
    +
    + JobClient jc = new JobClient(conf);
    + RunningJob job = jc.submitJob(conf);
    + ...
    + 
    ]]> +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + RecordReader's for CombineFileSplit's. + @see CombineFileSplit]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + CombineFileRecordReader. + + Subclassing is needed to get a concrete record reader wrapper because of the + constructor requirement. + + @see CombineFileRecordReader + @see CombineFileInputFormat]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + CombineFileInputFormat-equivalent for + SequenceFileInputFormat. + + @see CombineFileInputFormat]]> + + + + + + + + + + + + + + + CombineFileInputFormat-equivalent for + TextInputFormat. + + @see CombineFileInputFormat]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true if the name output is multi, false + if it is single. If the name output is not defined it returns + false]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + By default these counters are disabled. +

    + MultipleOutputs supports counters, by default the are disabled. + The counters group is the {@link MultipleOutputs} class name. +

    + The names of the counters are the same as the named outputs. For multi + named outputs the name of the counter is the concatenation of the named + output, and underscore '_' and the multiname. + + @param conf job conf to enableadd the named output. + @param enabled indicates if the counters will be enabled or not.]]> +
    +
    + + + + + By default these counters are disabled. +

    + MultipleOutputs supports counters, by default the are disabled. + The counters group is the {@link MultipleOutputs} class name. +

    + The names of the counters are the same as the named outputs. For multi + named outputs the name of the counter is the concatenation of the named + output, and underscore '_' and the multiname. + + + @param conf job conf to enableadd the named output. + @return TRUE if the counters are enabled, FALSE if they are disabled.]]> +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + If overriden subclasses must invoke super.close() at the + end of their close() + + @throws java.io.IOException thrown if any of the MultipleOutput files + could not be closed properly.]]> + + + + OutputCollector passed to + the map() and reduce() methods of the + Mapper and Reducer implementations. +

    + Each additional output, or named output, may be configured with its own + OutputFormat, with its own key class and with its own value + class. +

    + A named output can be a single file or a multi file. The later is refered as + a multi named output. +

    + A multi named output is an unbound set of files all sharing the same + OutputFormat, key class and value class configuration. +

    + When named outputs are used within a Mapper implementation, + key/values written to a name output are not part of the reduce phase, only + key/values written to the job OutputCollector are part of the + reduce phase. +

    + MultipleOutputs supports counters, by default the are disabled. The counters + group is the {@link MultipleOutputs} class name. +

    + The names of the counters are the same as the named outputs. For multi + named outputs the name of the counter is the concatenation of the named + output, and underscore '_' and the multiname. +

    + Job configuration usage pattern is: +

    +
    + JobConf conf = new JobConf();
    +
    + conf.setInputPath(inDir);
    + FileOutputFormat.setOutputPath(conf, outDir);
    +
    + conf.setMapperClass(MOMap.class);
    + conf.setReducerClass(MOReduce.class);
    + ...
    +
    + // Defines additional single text based output 'text' for the job
    + MultipleOutputs.addNamedOutput(conf, "text", TextOutputFormat.class,
    + LongWritable.class, Text.class);
    +
    + // Defines additional multi sequencefile based output 'sequence' for the
    + // job
    + MultipleOutputs.addMultiNamedOutput(conf, "seq",
    +   SequenceFileOutputFormat.class,
    +   LongWritable.class, Text.class);
    + ...
    +
    + JobClient jc = new JobClient();
    + RunningJob job = jc.submitJob(conf);
    +
    + ...
    + 
    +

    + Job configuration usage pattern is: +

    +
    + public class MOReduce implements
    +   Reducer<WritableComparable, Writable> {
    + private MultipleOutputs mos;
    +
    + public void configure(JobConf conf) {
    + ...
    + mos = new MultipleOutputs(conf);
    + }
    +
    + public void reduce(WritableComparable key, Iterator<Writable> values,
    + OutputCollector output, Reporter reporter)
    + throws IOException {
    + ...
    + mos.getCollector("text", reporter).collect(key, new Text("Hello"));
    + mos.getCollector("seq", "A", reporter).collect(key, new Text("Bye"));
    + mos.getCollector("seq", "B", reporter).collect(key, new Text("Chau"));
    + ...
    + }
    +
    + public void close() throws IOException {
    + mos.close();
    + ...
    + }
    +
    + }
    + 
    ]]> +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + It can be used instead of the default implementation, + of {@link org.apache.hadoop.mapred.MapRunner}, when the Map + operation is not CPU bound in order to improve throughput. +

    + Map implementations using this MapRunnable must be thread-safe. +

    + The Map-Reduce job has to be configured to use this MapRunnable class (using + the JobConf.setMapRunnerClass method) and + the number of threads the thread-pool can use with the + mapred.map.multithreadedrunner.threads property, its default + value is 10 threads. +

    ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + R reduces, there are R-1 + keys in the SequenceFile. + @deprecated Use + {@link #setPartitionFile(Configuration, Path)} + instead]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Cluster. + @throws IOException]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ClusterMetrics provides clients with information such as: +

      +
    1. + Size of the cluster. +
    2. +
    3. + Number of blacklisted and decommissioned trackers. +
    4. +
    5. + Slot capacity of the cluster. +
    6. +
    7. + The number of currently occupied/reserved map and reduce slots. +
    8. +
    9. + The number of currently running map and reduce tasks. +
    10. +
    11. + The number of job submissions. +
    12. +
    + +

    Clients can query for the latest ClusterMetrics, via + {@link Cluster#getClusterStatus()}.

    + + @see Cluster]]> +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Counters represent global counters, defined either by the + Map-Reduce framework or applications. Each Counter is named by + an {@link Enum} and has a long for the value.

    + +

    Counters are bunched into Groups, each comprising of + counters from a particular Enum class.]]> + + + + + + + + + + + + + + + + + + + + + the type of counter + @param the type of counter group + @param counters the old counters object]]> + + + + Counters holds per job/task counters, defined either by the + Map-Reduce framework or applications. Each Counter can be of + any {@link Enum} type.

    + +

    Counters are bunched into {@link CounterGroup}s, each + comprising of counters from a particular Enum class.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Each {@link InputSplit} is then assigned to an individual {@link Mapper} + for processing.

    + +

    Note: The split is a logical split of the inputs and the + input files are not physically split into chunks. For e.g. a split could + be <input-file-path, start, offset> tuple. The InputFormat + also creates the {@link RecordReader} to read the {@link InputSplit}. + + @param context job configuration. + @return an array of {@link InputSplit}s for the job.]]> + + + + + + + + + + + + + InputFormat describes the input-specification for a + Map-Reduce job. + +

    The Map-Reduce framework relies on the InputFormat of the + job to:

    +

      +
    1. + Validate the input-specification of the job. +
    2. + Split-up the input file(s) into logical {@link InputSplit}s, each of + which is then assigned to an individual {@link Mapper}. +
    3. +
    4. + Provide the {@link RecordReader} implementation to be used to glean + input records from the logical InputSplit for processing by + the {@link Mapper}. +
    5. +
    + +

    The default behavior of file-based {@link InputFormat}s, typically + sub-classes of {@link FileInputFormat}, is to split the + input into logical {@link InputSplit}s based on the total size, in + bytes, of the input files. However, the {@link FileSystem} blocksize of + the input files is treated as an upper bound for input splits. A lower bound + on the split size can be set via + + mapreduce.input.fileinputformat.split.minsize.

    + +

    Clearly, logical splits based on input-size is insufficient for many + applications since record boundaries are to respected. In such cases, the + application has to also implement a {@link RecordReader} on whom lies the + responsibility to respect record-boundaries and present a record-oriented + view of the logical InputSplit to the individual task. + + @see InputSplit + @see RecordReader + @see FileInputFormat]]> + + + + + + + + + + + + + + + + + + + + + + + + + SplitLocationInfos describing how the split + data is stored at each location. A null value indicates that all the + locations have the data stored on disk. + @throws IOException]]> + + + + InputSplit represents the data to be processed by an + individual {@link Mapper}. + +

    Typically, it presents a byte-oriented view on the input and is the + responsibility of {@link RecordReader} of the job to process this and present + a record-oriented view. + + @see InputFormat + @see RecordReader]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Job makes a copy of the Configuration so + that any necessary internal modifications do not reflect on the incoming + parameter. + + A Cluster will be created from the conf parameter only when it's needed. + + @param conf the configuration + @return the {@link Job} , with no connection to a cluster yet. + @throws IOException]]> + + + + + + + + Job makes a copy of the Configuration so + that any necessary internal modifications do not reflect on the incoming + parameter. + + @param conf the configuration + @return the {@link Job} , with no connection to a cluster yet. + @throws IOException]]> + + + + + + + + Job makes a copy of the Configuration so + that any necessary internal modifications do not reflect on the incoming + parameter. + + @param status job status + @param conf job configuration + @return the {@link Job} , with no connection to a cluster yet. + @throws IOException]]> + + + + + + + Job makes a copy of the Configuration so + that any necessary internal modifications do not reflect on the incoming + parameter. + + @param ignored + @return the {@link Job} , with no connection to a cluster yet. + @throws IOException + @deprecated Use {@link #getInstance()}]]> + + + + + + + + Job makes a copy of the Configuration so + that any necessary internal modifications do not reflect on the incoming + parameter. + + @param ignored + @param conf job configuration + @return the {@link Job} , with no connection to a cluster yet. + @throws IOException + @deprecated Use {@link #getInstance(Configuration)}]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + progress of the job's map-tasks, as a float between 0.0 + and 1.0. When all map tasks have completed, the function returns 1.0. + + @return the progress of the job's map-tasks. + @throws IOException]]> + + + + + + progress of the job's reduce-tasks, as a float between 0.0 + and 1.0. When all reduce tasks have completed, the function returns 1.0. + + @return the progress of the job's reduce-tasks. + @throws IOException]]> + + + + + + + progress of the job's cleanup-tasks, as a float between 0.0 + and 1.0. When all cleanup tasks have completed, the function returns 1.0. + + @return the progress of the job's cleanup-tasks. + @throws IOException]]> + + + + + + progress of the job's setup-tasks, as a float between 0.0 + and 1.0. When all setup tasks have completed, the function returns 1.0. + + @return the progress of the job's setup-tasks. + @throws IOException]]> + + + + + + true if the job is complete, else false. + @throws IOException]]> + + + + + + true if the job succeeded, else false. + @throws IOException]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + InputFormat to use + @throws IllegalStateException if the job is submitted]]> + + + + + + + OutputFormat to use + @throws IllegalStateException if the job is submitted]]> + + + + + + + Mapper to use + @throws IllegalStateException if the job is submitted]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Reducer to use + @throws IllegalStateException if the job is submitted]]> + + + + + + + Partitioner to use + @throws IllegalStateException if the job is submitted]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true if speculative execution + should be turned on, else false.]]> + + + + + + true if speculative execution + should be turned on for map tasks, + else false.]]> + + + + + + true if speculative execution + should be turned on for reduce tasks, + else false.]]> + + + + + + true, job-setup and job-cleanup will be + considered from {@link OutputCommitter} + else ignored.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + JobTracker is lost]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + It allows the user to configure the + job, submit it, control its execution, and query the state. The set methods + only work until the job is submitted, afterwards they will throw an + IllegalStateException.

    + +

    + Normally the user creates the application, describes various facets of the + job via {@link Job} and then submits the job and monitor its progress.

    + +

    Here is an example on how to submit a job:

    +

    +     // Create a new Job
    +     Job job = Job.getInstance();
    +     job.setJarByClass(MyJob.class);
    +     
    +     // Specify various job-specific parameters     
    +     job.setJobName("myjob");
    +     
    +     job.setInputPath(new Path("in"));
    +     job.setOutputPath(new Path("out"));
    +     
    +     job.setMapperClass(MyJob.MyMapper.class);
    +     job.setReducerClass(MyJob.MyReducer.class);
    +
    +     // Submit the job, then poll for progress until the job is complete
    +     job.waitForCompletion(true);
    + 
    ]]> +
    + + + + + + + + + + + + + + + + + + + + + + + 1. + @return the number of reduce tasks for this job.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + mapred.map.max.attempts + property. If this property is not already set, the default is 4 attempts. + + @return the max number of attempts per map task.]]> + + + + + mapred.reduce.max.attempts + property. If this property is not already set, the default is 4 attempts. + + @return the max number of attempts per reduce task.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + An example JobID is : + job_200707121733_0003 , which represents the third job + running at the jobtracker started at 200707121733. +

    + Applications should never construct or parse JobID strings, but rather + use appropriate constructors or {@link #forName(String)} method. + + @see TaskID + @see TaskAttemptID]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + the key input type to the Mapper + @param the value input type to the Mapper + @param the key output type from the Mapper + @param the value output type from the Mapper]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Maps are the individual tasks which transform input records into a + intermediate records. The transformed intermediate records need not be of + the same type as the input records. A given input pair may map to zero or + many output pairs.

    + +

    The Hadoop Map-Reduce framework spawns one map task for each + {@link InputSplit} generated by the {@link InputFormat} for the job. + Mapper implementations can access the {@link Configuration} for + the job via the {@link JobContext#getConfiguration()}. + +

    The framework first calls + {@link #setup(org.apache.hadoop.mapreduce.Mapper.Context)}, followed by + {@link #map(Object, Object, org.apache.hadoop.mapreduce.Mapper.Context)} + for each key/value pair in the InputSplit. Finally + {@link #cleanup(org.apache.hadoop.mapreduce.Mapper.Context)} is called.

    + +

    All intermediate values associated with a given output key are + subsequently grouped by the framework, and passed to a {@link Reducer} to + determine the final output. Users can control the sorting and grouping by + specifying two key {@link RawComparator} classes.

    + +

    The Mapper outputs are partitioned per + Reducer. Users can control which keys (and hence records) go to + which Reducer by implementing a custom {@link Partitioner}. + +

    Users can optionally specify a combiner, via + {@link Job#setCombinerClass(Class)}, to perform local aggregation of the + intermediate outputs, which helps to cut down the amount of data transferred + from the Mapper to the Reducer. + +

    Applications can specify if and how the intermediate + outputs are to be compressed and which {@link CompressionCodec}s are to be + used via the Configuration.

    + +

    If the job has zero + reduces then the output of the Mapper is directly written + to the {@link OutputFormat} without sorting by keys.

    + +

    Example:

    +

    + public class TokenCounterMapper 
    +     extends Mapper<Object, Text, Text, IntWritable>{
    +    
    +   private final static IntWritable one = new IntWritable(1);
    +   private Text word = new Text();
    +   
    +   public void map(Object key, Text value, Context context) throws IOException, InterruptedException {
    +     StringTokenizer itr = new StringTokenizer(value.toString());
    +     while (itr.hasMoreTokens()) {
    +       word.set(itr.nextToken());
    +       context.write(word, one);
    +     }
    +   }
    + }
    + 
    + +

    Applications may override the + {@link #run(org.apache.hadoop.mapreduce.Mapper.Context)} method to exert + greater control on map processing e.g. multi-threaded Mappers + etc.

    + + @see InputFormat + @see JobContext + @see Partitioner + @see Reducer]]> +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + MarkableIterator is a wrapper iterator class that + implements the {@link MarkableIteratorInterface}.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true if task output recovery is supported, + false otherwise + @see #recoverTask(TaskAttemptContext) + @deprecated Use {@link #isRecoverySupported(JobContext)} instead.]]> + + + + + + + true repeatable job commit is supported, + false otherwise + @throws IOException]]> + + + + + + + true if task output recovery is supported, + false otherwise + @throws IOException + @see #recoverTask(TaskAttemptContext)]]> + + + + + + + OutputCommitter. This is called from the application master + process, but it is called individually for each task. + + If an exception is thrown the task will be attempted again. + + This may be called multiple times for the same task. But from different + application attempts. + + @param taskContext Context of the task whose output is being recovered + @throws IOException]]> + + + + OutputCommitter describes the commit of task output for a + Map-Reduce job. + +

    The Map-Reduce framework relies on the OutputCommitter of + the job to:

    +

      +
    1. + Setup the job during initialization. For example, create the temporary + output directory for the job during the initialization of the job. +
    2. +
    3. + Cleanup the job after the job completion. For example, remove the + temporary output directory after the job completion. +
    4. +
    5. + Setup the task temporary output. +
    6. +
    7. + Check whether a task needs a commit. This is to avoid the commit + procedure if a task does not need commit. +
    8. +
    9. + Commit of the task output. +
    10. +
    11. + Discard the task commit. +
    12. +
    + The methods in this class can be called from several different processes and + from several different contexts. It is important to know which process and + which context each is called from. Each method should be marked accordingly + in its documentation. It is also important to note that not all methods are + guaranteed to be called once and only once. If a method is not guaranteed to + have this property the output committer needs to handle this appropriately. + Also note it will only be in rare situations where they may be called + multiple times for the same task. + + @see org.apache.hadoop.mapreduce.lib.output.FileOutputCommitter + @see JobContext + @see TaskAttemptContext]]> +
    +
    + + + + + + + + + + + + + + + + + + + This is to validate the output specification for the job when it is + a job is submitted. Typically checks that it does not already exist, + throwing an exception when it already exists, so that output is not + overwritten.

    + + @param context information about the job + @throws IOException when output should not be attempted]]> +
    +
    + + + + + + + + + + OutputFormat describes the output-specification for a + Map-Reduce job. + +

    The Map-Reduce framework relies on the OutputFormat of the + job to:

    +

      +
    1. + Validate the output-specification of the job. For e.g. check that the + output directory doesn't already exist. +
    2. + Provide the {@link RecordWriter} implementation to be used to write out + the output files of the job. Output files are stored in a + {@link FileSystem}. +
    3. +
    + + @see RecordWriter]]> +
    +
    + + + + + + + + + + + Typically a hash function on a all or a subset of the key.

    + + @param key the key to be partioned. + @param value the entry value. + @param numPartitions the total number of partitions. + @return the partition number for the key.]]> +
    +
    + + Partitioner controls the partitioning of the keys of the + intermediate map-outputs. The key (or a subset of the key) is used to derive + the partition, typically by a hash function. The total number of partitions + is the same as the number of reduce tasks for the job. Hence this controls + which of the m reduce tasks the intermediate key (and hence the + record) is sent for reduction.

    + + Note: If you require your Partitioner class to obtain the Job's configuration + object, implement the {@link Configurable} interface. + + @see Reducer]]> +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "N/A" + + @return Scheduling information associated to particular Job Queue]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @param ]]> + + + + + + + + + + + + + + + + + + + + + + RecordWriter to future operations. + + @param context the context of the task + @throws IOException]]> + + + + RecordWriter writes the output <key, value> pairs + to an output file. + +

    RecordWriter implementations write the job outputs to the + {@link FileSystem}. + + @see OutputFormat]]> + + + + + + + + + + + + + + + + + + + + + + the class of the input keys + @param the class of the input values + @param the class of the output keys + @param the class of the output values]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Reducer implementations + can access the {@link Configuration} for the job via the + {@link JobContext#getConfiguration()} method.

    + +

    Reducer has 3 primary phases:

    +
      +
    1. + + Shuffle + +

      The Reducer copies the sorted output from each + {@link Mapper} using HTTP across the network.

      +
    2. + +
    3. + Sort + +

      The framework merge sorts Reducer inputs by + keys + (since different Mappers may have output the same key).

      + +

      The shuffle and sort phases occur simultaneously i.e. while outputs are + being fetched they are merged.

      + + SecondarySort + +

      To achieve a secondary sort on the values returned by the value + iterator, the application should extend the key with the secondary + key and define a grouping comparator. The keys will be sorted using the + entire key, but will be grouped using the grouping comparator to decide + which keys and values are sent in the same call to reduce.The grouping + comparator is specified via + {@link Job#setGroupingComparatorClass(Class)}. The sort order is + controlled by + {@link Job#setSortComparatorClass(Class)}.

      + + + For example, say that you want to find duplicate web pages and tag them + all with the url of the "best" known example. You would set up the job + like: +
        +
      • Map Input Key: url
      • +
      • Map Input Value: document
      • +
      • Map Output Key: document checksum, url pagerank
      • +
      • Map Output Value: url
      • +
      • Partitioner: by checksum
      • +
      • OutputKeyComparator: by checksum and then decreasing pagerank
      • +
      • OutputValueGroupingComparator: by checksum
      • +
      +
    4. + +
    5. + Reduce + +

      In this phase the + {@link #reduce(Object, Iterable, org.apache.hadoop.mapreduce.Reducer.Context)} + method is called for each <key, (collection of values)> in + the sorted inputs.

      +

      The output of the reduce task is typically written to a + {@link RecordWriter} via + {@link Context#write(Object, Object)}.

      +
    6. +
    + +

    The output of the Reducer is not re-sorted.

    + +

    Example:

    +

    + public class IntSumReducer<Key> extends Reducer<Key,IntWritable,
    +                                                 Key,IntWritable> {
    +   private IntWritable result = new IntWritable();
    + 
    +   public void reduce(Key key, Iterable<IntWritable> values,
    +                      Context context) throws IOException, InterruptedException {
    +     int sum = 0;
    +     for (IntWritable val : values) {
    +       sum += val.get();
    +     }
    +     result.set(sum);
    +     context.write(key, result);
    +   }
    + }
    + 
    + + @see Mapper + @see Partitioner]]> +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + counterName. + @param counterName counter name + @return the Counter for the given counterName]]> + + + + + + + groupName and + counterName. + @param counterName counter name + @return the Counter for the given groupName and + counterName]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + An example TaskAttemptID is : + attempt_200707121733_0003_m_000005_0 , which represents the + zeroth task attempt for the fifth map task in the third job + running at the jobtracker started at 200707121733. +

    + Applications should never construct or parse TaskAttemptID strings + , but rather use appropriate constructors or {@link #forName(String)} + method. + + @see JobID + @see TaskID]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + An example TaskID is : + task_200707121733_0003_m_000005 , which represents the + fifth map task in the third job running at the jobtracker + started at 200707121733. +

    + Applications should never construct or parse TaskID strings + , but rather use appropriate constructors or {@link #forName(String)} + method. + + @see JobID + @see TaskAttemptID]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + OutputCommitter for the task-attempt]]> + + + + the input key type for the task + @param the input value type for the task + @param the output key type for the task + @param the output value type for the task]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + type of the other counter + @param type of the other counter group + @param counters the counters object to copy + @param groupFactory the factory for new groups]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + type of counter inside the counters + @param type of group inside the counters]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + type of the counter for the group]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The key and values are passed from one element of the chain to the next, by + value. For the added Mapper the configuration given for it, + mapperConf, have precedence over the job's Configuration. This + precedence is in effect when the task is running. +

    +

    + IMPORTANT: There is no need to specify the output key/value classes for the + ChainMapper, this is done by the addMapper for the last mapper in the chain +

    + + @param job + The job. + @param klass + the Mapper class to add. + @param inputKeyClass + mapper input key class. + @param inputValueClass + mapper input value class. + @param outputKeyClass + mapper output key class. + @param outputValueClass + mapper output value class. + @param mapperConf + a configuration for the Mapper class. It is recommended to use a + Configuration without default values using the + Configuration(boolean loadDefaults) constructor with + FALSE.]]> +
    + + + + + + + + + + + + The Mapper classes are invoked in a chained (or piped) fashion, the output of + the first becomes the input of the second, and so on until the last Mapper, + the output of the last Mapper will be written to the task's output. +

    +

    + The key functionality of this feature is that the Mappers in the chain do not + need to be aware that they are executed in a chain. This enables having + reusable specialized Mappers that can be combined to perform composite + operations within a single task. +

    +

    + Special care has to be taken when creating chains that the key/values output + by a Mapper are valid for the following Mapper in the chain. It is assumed + all Mappers and the Reduce in the chain use matching output and input key and + value classes as no conversion is done by the chaining code. +

    +

    + Using the ChainMapper and the ChainReducer classes is possible to compose + Map/Reduce jobs that look like [MAP+ / REDUCE MAP*]. And + immediate benefit of this pattern is a dramatic reduction in disk IO. +

    +

    + IMPORTANT: There is no need to specify the output key/value classes for the + ChainMapper, this is done by the addMapper for the last mapper in the chain. +

    + ChainMapper usage pattern: +

    + +

    + ...
    + Job = new Job(conf);
    +
    + Configuration mapAConf = new Configuration(false);
    + ...
    + ChainMapper.addMapper(job, AMap.class, LongWritable.class, Text.class,
    +   Text.class, Text.class, true, mapAConf);
    +
    + Configuration mapBConf = new Configuration(false);
    + ...
    + ChainMapper.addMapper(job, BMap.class, Text.class, Text.class,
    +   LongWritable.class, Text.class, false, mapBConf);
    +
    + ...
    +
    + job.waitForComplettion(true);
    + ...
    + 
    ]]> +
    +
    + + + + + + + + + + + + + + + + The key and values are passed from one element of the chain to the next, by + value. For the added Reducer the configuration given for it, + reducerConf, have precedence over the job's Configuration. + This precedence is in effect when the task is running. +

    +

    + IMPORTANT: There is no need to specify the output key/value classes for the + ChainReducer, this is done by the setReducer or the addMapper for the last + element in the chain. +

    + + @param job + the job + @param klass + the Reducer class to add. + @param inputKeyClass + reducer input key class. + @param inputValueClass + reducer input value class. + @param outputKeyClass + reducer output key class. + @param outputValueClass + reducer output value class. + @param reducerConf + a configuration for the Reducer class. It is recommended to use a + Configuration without default values using the + Configuration(boolean loadDefaults) constructor with + FALSE.]]> +
    +
    + + + + + + + + + + + + The key and values are passed from one element of the chain to the next, by + value For the added Mapper the configuration given for it, + mapperConf, have precedence over the job's Configuration. This + precedence is in effect when the task is running. +

    +

    + IMPORTANT: There is no need to specify the output key/value classes for the + ChainMapper, this is done by the addMapper for the last mapper in the + chain. +

    + + @param job + The job. + @param klass + the Mapper class to add. + @param inputKeyClass + mapper input key class. + @param inputValueClass + mapper input value class. + @param outputKeyClass + mapper output key class. + @param outputValueClass + mapper output value class. + @param mapperConf + a configuration for the Mapper class. It is recommended to use a + Configuration without default values using the + Configuration(boolean loadDefaults) constructor with + FALSE.]]> +
    +
    + + + + + + + + + + + For each record output by the Reducer, the Mapper classes are invoked in a + chained (or piped) fashion. The output of the reducer becomes the input of + the first mapper and output of first becomes the input of the second, and so + on until the last Mapper, the output of the last Mapper will be written to + the task's output. +

    +

    + The key functionality of this feature is that the Mappers in the chain do not + need to be aware that they are executed after the Reducer or in a chain. This + enables having reusable specialized Mappers that can be combined to perform + composite operations within a single task. +

    +

    + Special care has to be taken when creating chains that the key/values output + by a Mapper are valid for the following Mapper in the chain. It is assumed + all Mappers and the Reduce in the chain use matching output and input key and + value classes as no conversion is done by the chaining code. +

    +

    Using the ChainMapper and the ChainReducer classes is possible to + compose Map/Reduce jobs that look like [MAP+ / REDUCE MAP*]. And + immediate benefit of this pattern is a dramatic reduction in disk IO.

    +

    + IMPORTANT: There is no need to specify the output key/value classes for the + ChainReducer, this is done by the setReducer or the addMapper for the last + element in the chain. +

    + ChainReducer usage pattern: +

    + +

    + ...
    + Job = new Job(conf);
    + ....
    +
    + Configuration reduceConf = new Configuration(false);
    + ...
    + ChainReducer.setReducer(job, XReduce.class, LongWritable.class, Text.class,
    +   Text.class, Text.class, true, reduceConf);
    +
    + ChainReducer.addMapper(job, CMap.class, Text.class, Text.class,
    +   LongWritable.class, Text.class, false, null);
    +
    + ChainReducer.addMapper(job, DMap.class, LongWritable.class, Text.class,
    +   LongWritable.class, LongWritable.class, true, null);
    +
    + ...
    +
    + job.waitForCompletion(true);
    + ...
    + 
    ]]> +
    +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DBInputFormat emits LongWritables containing the record number as + key and DBWritables as value. + + The SQL query, and input class can be using one of the two + setInput methods.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {@link DBOutputFormat} accepts <key,value> pairs, where + key has a type extending DBWritable. Returned {@link RecordWriter} + writes only the key to the database with a batch SQL query.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DBWritable. DBWritable, is similar to {@link Writable} + except that the {@link #write(PreparedStatement)} method takes a + {@link PreparedStatement}, and {@link #readFields(ResultSet)} + takes a {@link ResultSet}. +

    + Implementations are responsible for writing the fields of the object + to PreparedStatement, and reading the fields of the object from the + ResultSet. + +

    Example:

    + If we have the following table in the database : +
    + CREATE TABLE MyTable (
    +   counter        INTEGER NOT NULL,
    +   timestamp      BIGINT  NOT NULL,
    + );
    + 
    + then we can read/write the tuples from/to the table with : +

    + public class MyWritable implements Writable, DBWritable {
    +   // Some data     
    +   private int counter;
    +   private long timestamp;
    +       
    +   //Writable#write() implementation
    +   public void write(DataOutput out) throws IOException {
    +     out.writeInt(counter);
    +     out.writeLong(timestamp);
    +   }
    +       
    +   //Writable#readFields() implementation
    +   public void readFields(DataInput in) throws IOException {
    +     counter = in.readInt();
    +     timestamp = in.readLong();
    +   }
    +       
    +   public void write(PreparedStatement statement) throws SQLException {
    +     statement.setInt(1, counter);
    +     statement.setLong(2, timestamp);
    +   }
    +       
    +   public void readFields(ResultSet resultSet) throws SQLException {
    +     counter = resultSet.getInt(1);
    +     timestamp = resultSet.getLong(2);
    +   } 
    + }
    + 
    ]]> +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + RecordReader's for + CombineFileSplit's. + + @see CombineFileSplit]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + CombineFileRecordReader. + + Subclassing is needed to get a concrete record reader wrapper because of the + constructor requirement. + + @see CombineFileRecordReader + @see CombineFileInputFormat]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + th Path]]> + + + + + + th Path]]> + + + + + + + + + + + th Path]]> + + + + + + + + + + + + + + + + + + + + + + + + + + CombineFileSplit can be used to implement {@link RecordReader}'s, + with reading one record per file. + + @see FileSplit + @see CombineFileInputFormat]]> + + + + + + + + + + + + + + CombineFileInputFormat-equivalent for + SequenceFileInputFormat. + + @see CombineFileInputFormat]]> + + + + + + + + + + + + + + CombineFileInputFormat-equivalent for + TextInputFormat. + + @see CombineFileInputFormat]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + FileInputFormat always returns + true. Implementations that may deal with non-splittable files must + override this method. + + FileInputFormat implementations can override this and return + false to ensure that individual input files are never split-up + so that {@link Mapper}s process entire files. + + @param context the job context + @param filename the file name to check + @return is this file splitable?]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + FileInputFormat is the base class for all file-based + InputFormats. This provides a generic implementation of + {@link #getSplits(JobContext)}. + + Implementations of FileInputFormat can also override the + {@link #isSplitable(JobContext, Path)} method to prevent input files + from being split-up in certain situations. Implementations that may + deal with non-splittable files must override this method, since + the default implementation assumes splitting is always possible.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    or + conf.setInt(FixedLengthInputFormat.FIXED_RECORD_LENGTH, recordLength); +

    + @see FixedLengthRecordReader]]> +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true if the Job was added.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ([,]*) + func ::= tbl(,"") + class ::= @see java.lang.Class#forName(java.lang.String) + path ::= @see org.apache.hadoop.fs.Path#Path(java.lang.String) + } + Reads expression from the mapreduce.join.expr property and + user-supplied join types from mapreduce.join.define.<ident> + types. Paths supplied to tbl are given as input paths to the + InputFormat class listed. + @see #compose(java.lang.String, java.lang.Class, java.lang.String...)]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ,

    ) }]]> + + + + + + + + (tbl(,),tbl(,),...,tbl(,)) }]]> + + + + + + + + (tbl(,),tbl(,),...,tbl(,)) }]]> + + + + + + + + mapreduce.join.define.<ident> to a classname. + In the expression mapreduce.join.expr, the identifier will be + assumed to be a ComposableRecordReader. + mapreduce.join.keycomparator can be a classname used to compare + keys in the join. + @see #setFormat + @see JoinRecordReader + @see MultiFilterRecordReader]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ...... + }]]> + + + + + + + + + + + + + + + + + + + + + capacity children to position + id in the parent reader. + The id of a root CompositeRecordReader is -1 by convention, but relying + on this is not recommended.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + override(S1,S2,S3) will prefer values + from S3 over S2, and values from S2 over S1 for all keys + emitted from all sources.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [<child1>,<child2>,...,<childn>]]]> + + + + + + + out. + TupleWritable format: + {@code + ...... + }]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + the map's input key type + @param the map's input value type + @param the map's output key type + @param the map's output value type + @param job the job + @return the mapper class to run]]> + + + + + + + the map input key type + @param the map input value type + @param the map output key type + @param the map output value type + @param job the job to modify + @param cls the class to use as the mapper]]> + + + + + + + + + + + + + + + + + It can be used instead of the default implementation, + {@link org.apache.hadoop.mapred.MapRunner}, when the Map operation is not CPU + bound in order to improve throughput. +

    + Mapper implementations using this MapRunnable must be thread-safe. +

    + The Map-Reduce job has to be configured with the mapper to use via + {@link #setMapperClass(Job, Class)} and + the number of thread the thread-pool can use with the + {@link #getNumberOfThreads(JobContext)} method. The default + value is 10 threads. +

    ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + MapContext to be wrapped + @return a wrapped Mapper.Context for custom implementations]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true if the job output should be compressed, + false otherwise]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Tasks' Side-Effect Files + +

    Some applications need to create/write-to side-files, which differ from + the actual job-outputs. + +

    In such cases there could be issues with 2 instances of the same TIP + (running simultaneously e.g. speculative tasks) trying to open/write-to the + same file (path) on HDFS. Hence the application-writer will have to pick + unique names per task-attempt (e.g. using the attemptid, say + attempt_200709221812_0001_m_000000_0), not just per TIP.

    + +

    To get around this the Map-Reduce framework helps the application-writer + out by maintaining a special + ${mapreduce.output.fileoutputformat.outputdir}/_temporary/_${taskid} + sub-directory for each task-attempt on HDFS where the output of the + task-attempt goes. On successful completion of the task-attempt the files + in the ${mapreduce.output.fileoutputformat.outputdir}/_temporary/_${taskid} (only) + are promoted to ${mapreduce.output.fileoutputformat.outputdir}. Of course, the + framework discards the sub-directory of unsuccessful task-attempts. This + is completely transparent to the application.

    + +

    The application-writer can take advantage of this by creating any + side-files required in a work directory during execution + of his task i.e. via + {@link #getWorkOutputPath(TaskInputOutputContext)}, and + the framework will move them out similarly - thus she doesn't have to pick + unique paths per task-attempt.

    + +

    The entire discussion holds true for maps of jobs with + reducer=NONE (i.e. 0 reduces) since output of the map, in that case, + goes directly to HDFS.

    + + @return the {@link Path} to the task's temporary output directory + for the map-reduce job.]]> +
    +
    + + + + + + + + The path can be used to create custom files from within the map and + reduce tasks. The path name will be unique for each task. The path parent + will be the job output directory.

    ls + +

    This method uses the {@link #getUniqueFile} method to make the file name + unique for the task.

    + + @param context the context for the task. + @param name the name for the file. + @param extension the extension for the file + @return a unique path accross all tasks of the job.]]> +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Warning: when the baseOutputPath is a path that resolves + outside of the final job output directory, the directory is created + immediately and then persists through subsequent task retries, breaking + the concept of output committing.]]> + + + + + + + + + + Warning: when the baseOutputPath is a path that resolves + outside of the final job output directory, the directory is created + immediately and then persists through subsequent task retries, breaking + the concept of output committing.]]> + + + + + + + super.close() at the + end of their close()]]> + + + + + Case one: writing to additional outputs other than the job default output. + + Each additional output, or named output, may be configured with its own + OutputFormat, with its own key class and with its own value + class. +

    + +

    + Case two: to write data to different files provided by user +

    + +

    + MultipleOutputs supports counters, by default they are disabled. The + counters group is the {@link MultipleOutputs} class name. The names of the + counters are the same as the output name. These count the number records + written to each output name. +

    + + Usage pattern for job submission: +
    +
    + Job job = new Job();
    +
    + FileInputFormat.setInputPath(job, inDir);
    + FileOutputFormat.setOutputPath(job, outDir);
    +
    + job.setMapperClass(MOMap.class);
    + job.setReducerClass(MOReduce.class);
    + ...
    +
    + // Defines additional single text based output 'text' for the job
    + MultipleOutputs.addNamedOutput(job, "text", TextOutputFormat.class,
    + LongWritable.class, Text.class);
    +
    + // Defines additional sequence-file based output 'sequence' for the job
    + MultipleOutputs.addNamedOutput(job, "seq",
    +   SequenceFileOutputFormat.class,
    +   LongWritable.class, Text.class);
    + ...
    +
    + job.waitForCompletion(true);
    + ...
    + 
    +

    + Usage in Reducer: +

    + <K, V> String generateFileName(K k, V v) {
    +   return k.toString() + "_" + v.toString();
    + }
    + 
    + public class MOReduce extends
    +   Reducer<WritableComparable, Writable,WritableComparable, Writable> {
    + private MultipleOutputs mos;
    + public void setup(Context context) {
    + ...
    + mos = new MultipleOutputs(context);
    + }
    +
    + public void reduce(WritableComparable key, Iterator<Writable> values,
    + Context context)
    + throws IOException {
    + ...
    + mos.write("text", , key, new Text("Hello"));
    + mos.write("seq", LongWritable(1), new Text("Bye"), "seq_a");
    + mos.write("seq", LongWritable(2), key, new Text("Chau"), "seq_b");
    + mos.write(key, new Text("value"), generateFileName(key, new Text("value")));
    + ...
    + }
    +
    + public void cleanup(Context) throws IOException {
    + mos.close();
    + ...
    + }
    +
    + }
    + 
    + +

    + When used in conjuction with org.apache.hadoop.mapreduce.lib.output.LazyOutputFormat, + MultipleOutputs can mimic the behaviour of MultipleTextOutputFormat and MultipleSequenceFileOutputFormat + from the old Hadoop API - ie, output can be written from the Reducer to more than one location. +

    + +

    + Use MultipleOutputs.write(KEYOUT key, VALUEOUT value, String baseOutputPath) to write key and + value to a path specified by baseOutputPath, with no need to specify a named output. + Warning: when the baseOutputPath passed to MultipleOutputs.write + is a path that resolves outside of the final job output directory, the + directory is created immediately and then persists through subsequent + task retries, breaking the concept of output committing: +

    + +
    + private MultipleOutputs<Text, Text> out;
    + 
    + public void setup(Context context) {
    +   out = new MultipleOutputs<Text, Text>(context);
    +   ...
    + }
    + 
    + public void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
    + for (Text t : values) {
    +   out.write(key, t, generateFileName(<parameter list...>));
    +   }
    + }
    + 
    + protected void cleanup(Context context) throws IOException, InterruptedException {
    +   out.close();
    + }
    + 
    + +

    + Use your own code in generateFileName() to create a custom path to your results. + '/' characters in baseOutputPath will be translated into directory levels in your file system. + Also, append your custom-generated path with "part" or similar, otherwise your output will be -00000, -00001 etc. + No call to context.write() is necessary. See example generateFileName() code below. +

    + +
    + private String generateFileName(Text k) {
    +   // expect Text k in format "Surname|Forename"
    +   String[] kStr = k.toString().split("\\|");
    +   
    +   String sName = kStr[0];
    +   String fName = kStr[1];
    +
    +   // example for k = Smith|John
    +   // output written to /user/hadoop/path/to/output/Smith/John-r-00000 (etc)
    +   return sName + "/" + fName;
    + }
    + 
    + +

    + Using MultipleOutputs in this way will still create zero-sized default output, eg part-00000. + To prevent this use LazyOutputFormat.setOutputFormatClass(job, TextOutputFormat.class); + instead of job.setOutputFormatClass(TextOutputFormat.class); in your Hadoop job configuration. +

    ]]> +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This allows the user to specify the key class to be different + from the actual class ({@link BytesWritable}) used for writing

    + + @param job the {@link Job} to modify + @param theClass the SequenceFile output key class.]]> +
    +
    + + + + + This allows the user to specify the value class to be different + from the actual class ({@link BytesWritable}) used for writing

    + + @param job the {@link Job} to modify + @param theClass the SequenceFile output key class.]]> +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + bytes[left:(right+1)] in Python syntax. + + @param conf configuration object + @param left left Python-style offset + @param right right Python-style offset]]> + + + + + + + bytes[offset:] in Python syntax. + + @param conf configuration object + @param offset left Python-style offset]]> + + + + + + + bytes[:(offset+1)] in Python syntax. + + @param conf configuration object + @param offset right Python-style offset]]> + + + + + + + + + + + + + + + + + + + + + Partition {@link BinaryComparable} keys using a configurable part of + the bytes array returned by {@link BinaryComparable#getBytes()}.

    + +

    The subarray to be used for the partitioning can be defined by means + of the following properties: +

      +
    • + mapreduce.partition.binarypartitioner.left.offset: + left offset in array (0 by default) +
    • +
    • + mapreduce.partition.binarypartitioner.right.offset: + right offset in array (-1 by default) +
    • +
    + Like in Python, both negative and positive offsets are allowed, but + the meaning is slightly different. In case of an array of length 5, + for instance, the possible offsets are: +
    
    +  +---+---+---+---+---+
    +  | B | B | B | B | B |
    +  +---+---+---+---+---+
    +    0   1   2   3   4
    +   -5  -4  -3  -2  -1
    + 
    + The first row of numbers gives the position of the offsets 0...5 in + the array; the second row gives the corresponding negative offsets. + Contrary to Python, the specified subarray has byte i + and j as first and last element, repectively, when + i and j are the left and right offset. + +

    For Hadoop programs written in Java, it is advisable to use one of + the following static convenience methods for setting the offsets: +

      +
    • {@link #setOffsets}
    • +
    • {@link #setLeftOffset}
    • +
    • {@link #setRightOffset}
    • +
    ]]> +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + total.order.partitioner.natural.order is not false, a trie + of the first total.order.partitioner.max.trie.depth(2) + 1 bytes + will be built. Otherwise, keys will be located using a binary search of + the partition keyset using the {@link org.apache.hadoop.io.RawComparator} + defined for this job. The input file must be sorted with the same + comparator and contain {@link Job#getNumReduceTasks()} - 1 keys.]]> + + + + + + + + + + + + + + R reduces, there are R-1 + keys in the SequenceFile.]]> + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ReduceContext to be wrapped + @return a wrapped Reducer.Context for custom implementations]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hadoop-mapreduce-project/dev-support/jdiff/Apache_Hadoop_MapReduce_JobClient_2.8.0.xml b/hadoop-mapreduce-project/dev-support/jdiff/Apache_Hadoop_MapReduce_JobClient_2.8.0.xml new file mode 100644 index 00000000000..82a6ef18531 --- /dev/null +++ b/hadoop-mapreduce-project/dev-support/jdiff/Apache_Hadoop_MapReduce_JobClient_2.8.0.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + diff --git a/hadoop-yarn-project/hadoop-yarn/dev-support/jdiff/Apache_Hadoop_YARN_Client_2.8.0.xml b/hadoop-yarn-project/hadoop-yarn/dev-support/jdiff/Apache_Hadoop_YARN_Client_2.8.0.xml new file mode 100644 index 00000000000..152601a536b --- /dev/null +++ b/hadoop-yarn-project/hadoop-yarn/dev-support/jdiff/Apache_Hadoop_YARN_Client_2.8.0.xml @@ -0,0 +1,2316 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + In secure mode, YARN verifies access to the application, queue + etc. before accepting the request. +

    + If the user does not have VIEW_APP access then the following + fields in the report will be set to stubbed values: +

      +
    • host - set to "N/A"
    • +
    • RPC port - set to -1
    • +
    • client token - set to "N/A"
    • +
    • diagnostics - set to "N/A"
    • +
    • tracking URL - set to "N/A"
    • +
    • original tracking URL - set to "N/A"
    • +
    • resource usage report - all values are -1
    • +
    + + @param appId + {@link ApplicationId} of the application that needs a report + @return application report + @throws YarnException + @throws IOException]]> +
    +
    + + + + + + Get a report (ApplicationReport) of all Applications in the cluster. +

    + +

    + If the user does not have VIEW_APP access for an application + then the corresponding report will be filtered as described in + {@link #getApplicationReport(ApplicationId)}. +

    + + @return a list of reports for all applications + @throws YarnException + @throws IOException]]> +
    +
    + + + + + + + Get a report of the given ApplicationAttempt. +

    + +

    + In secure mode, YARN verifies access to the application, queue + etc. before accepting the request. +

    + + @param applicationAttemptId + {@link ApplicationAttemptId} of the application attempt that needs + a report + @return application attempt report + @throws YarnException + @throws ApplicationAttemptNotFoundException if application attempt + not found + @throws IOException]]> +
    +
    + + + + + + + Get a report of all (ApplicationAttempts) of Application in the cluster. +

    + + @param applicationId + @return a list of reports for all application attempts for specified + application + @throws YarnException + @throws IOException]]> +
    +
    + + + + + + + Get a report of the given Container. +

    + +

    + In secure mode, YARN verifies access to the application, queue + etc. before accepting the request. +

    + + @param containerId + {@link ContainerId} of the container that needs a report + @return container report + @throws YarnException + @throws ContainerNotFoundException if container not found + @throws IOException]]> +
    +
    + + + + + + + Get a report of all (Containers) of ApplicationAttempt in the cluster. +

    + + @param applicationAttemptId + @return a list of reports of all containers for specified application + attempt + @throws YarnException + @throws IOException]]> +
    +
    +
    + + + + + + + + + {@code + AMRMClient.createAMRMClientContainerRequest() + } + @return the newly create AMRMClient instance.]]> + + + + + + + + + + RegisterApplicationMasterResponse + @throws YarnException + @throws IOException]]> + + + + + + + + addContainerRequest are sent to the + ResourceManager. New containers assigned to the master are + retrieved. Status of completed containers and node health updates are also + retrieved. This also doubles up as a heartbeat to the ResourceManager and + must be made periodically. The call may not always return any new + allocations of containers. App should not make concurrent allocate + requests. May cause request loss. + +

    + Note : If the user has not removed container requests that have already + been satisfied, then the re-register may end up sending the entire + container requests to the RM (including matched requests). Which would mean + the RM could end up giving it a lot of new allocated containers. +

    + + @param progressIndicator Indicates progress made by the master + @return the response of the allocate request + @throws YarnException + @throws IOException]]> +
    +
    + + + + + + + + + + + + + + allocate + @param req Resource request]]> + + + + + + + + + + + + + allocate. + Any previous pending resource change request of the same container will be + removed. + + Application that calls this method is expected to maintain the + Containers that are returned from previous successful + allocations or resource changes. By passing in the existing container and a + target resource capability to this method, the application requests the + ResourceManager to change the existing resource allocation to the target + resource allocation. + + @param container The container returned from the last successful resource + allocation or resource change + @param capability The target resource capability of the container]]> + + + + + + + + + + + + + + + + + + + + + + + + ContainerRequests matching the given + parameters. These ContainerRequests should have been added via + addContainerRequest earlier in the lifecycle. For performance, + the AMRMClient may return its internal collection directly without creating + a copy. Users should not perform mutable operations on the return value. + Each collection in the list contains requests with identical + Resource size that fit in the given capability. In a + collection, requests will be returned in the same order as they were added. + @return Collection of request matching the parameters]]> + + + + + + + + + + + + + AMRMClient. This cache must + be shared with the {@link NMClient} used to manage containers for the + AMRMClient +

    + If a NM token cache is not set, the {@link NMTokenCache#getSingleton()} + singleton instance will be used. + + @param nmTokenCache the NM token cache to use.]]> + + + + + AMRMClient. This cache must be + shared with the {@link NMClient} used to manage containers for the + AMRMClient. +

    + If a NM token cache is not set, the {@link NMTokenCache#getSingleton()} + singleton instance will be used. + + @return the NM token cache.]]> + + + + + + + check to return true for each 1000 ms. + See also {@link #waitFor(com.google.common.base.Supplier, int)} + and {@link #waitFor(com.google.common.base.Supplier, int, int)} + @param check]]> + + + + + + + + check to return true for each + checkEveryMillis ms. + See also {@link #waitFor(com.google.common.base.Supplier, int, int)} + @param check user defined checker + @param checkEveryMillis interval to call check]]> + + + + + + + + + check to return true for each + checkEveryMillis ms. In the main loop, this method will log + the message "waiting in main loop" for each logInterval times + iteration to confirm the thread is alive. + @param check user defined checker + @param checkEveryMillis interval to call check + @param logInterval interval to log for each]]> + + + + + + + + + + + + + + + + + + + + + + + + + + Start an allocated container.

    + +

    The ApplicationMaster or other applications that use the + client must provide the details of the allocated container, including the + Id, the assigned node's Id and the token via {@link Container}. In + addition, the AM needs to provide the {@link ContainerLaunchContext} as + well.

    + + @param container the allocated container + @param containerLaunchContext the context information needed by the + NodeManager to launch the + container + @return a map between the auxiliary service names and their outputs + @throws YarnException + @throws IOException]]> +
    +
    + + + + + + Increase the resource of a container.

    + +

    The ApplicationMaster or other applications that use the + client must provide the details of the container, including the Id and + the target resource encapsulated in the updated container token via + {@link Container}. +

    + + @param container the container with updated token + @throws YarnException + @throws IOException]]> +
    +
    + + + + + + + Stop an started container.

    + + @param containerId the Id of the started container + @param nodeId the Id of the NodeManager + + @throws YarnException + @throws IOException]]> +
    +
    + + + + + + + Query the status of a container.

    + + @param containerId the Id of the started container + @param nodeId the Id of the NodeManager + + @return the status of a container + @throws YarnException + @throws IOException]]> +
    +
    + + + + Set whether the containers that are started by this client, and are + still running should be stopped when the client stops. By default, the + feature should be enabled.

    However, containers will be stopped only + when service is stopped. i.e. after {@link NMClient#stop()}. + + @param enabled whether the feature is enabled or not]]> +
    +
    + + + + NMClient. This cache must be + shared with the {@link AMRMClient} that requested the containers managed + by this NMClient +

    + If a NM token cache is not set, the {@link NMTokenCache#getSingleton()} + singleton instance will be used. + + @param nmTokenCache the NM token cache to use.]]> + + + + + NMClient. This cache must be + shared with the {@link AMRMClient} that requested the containers managed + by this NMClient +

    + If a NM token cache is not set, the {@link NMTokenCache#getSingleton()} + singleton instance will be used. + + @return the NM token cache]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + By default Yarn client libraries {@link AMRMClient} and {@link NMClient} use + {@link #getSingleton()} instance of the cache. +

      +
    • + Using the singleton instance of the cache is appropriate when running a + single ApplicationMaster in the same JVM. +
    • +
    • + When using the singleton, users don't need to do anything special, + {@link AMRMClient} and {@link NMClient} are already set up to use the + default singleton {@link NMTokenCache} +
    • +
    + If running multiple Application Masters in the same JVM, a different cache + instance should be used for each Application Master. +
      +
    • + If using the {@link AMRMClient} and the {@link NMClient}, setting up + and using an instance cache is as follows: +
      +   NMTokenCache nmTokenCache = new NMTokenCache();
      +   AMRMClient rmClient = AMRMClient.createAMRMClient();
      +   NMClient nmClient = NMClient.createNMClient();
      +   nmClient.setNMTokenCache(nmTokenCache);
      +   ...
      + 
      +
    • +
    • + If using the {@link AMRMClientAsync} and the {@link NMClientAsync}, + setting up and using an instance cache is as follows: +
      +   NMTokenCache nmTokenCache = new NMTokenCache();
      +   AMRMClient rmClient = AMRMClient.createAMRMClient();
      +   NMClient nmClient = NMClient.createNMClient();
      +   nmClient.setNMTokenCache(nmTokenCache);
      +   AMRMClientAsync rmClientAsync = new AMRMClientAsync(rmClient, 1000, [AMRM_CALLBACK]);
      +   NMClientAsync nmClientAsync = new NMClientAsync("nmClient", nmClient, [NM_CALLBACK]);
      +   ...
      + 
      +
    • +
    • + If using {@link ApplicationMasterProtocol} and + {@link ContainerManagementProtocol} directly, setting up and using an + instance cache is as follows: +
      +   NMTokenCache nmTokenCache = new NMTokenCache();
      +   ...
      +   ApplicationMasterProtocol amPro = ClientRMProxy.createRMProxy(conf, ApplicationMasterProtocol.class);
      +   ...
      +   AllocateRequest allocateRequest = ...
      +   ...
      +   AllocateResponse allocateResponse = rmClient.allocate(allocateRequest);
      +   for (NMToken token : allocateResponse.getNMTokens()) {
      +     nmTokenCache.setToken(token.getNodeId().toString(), token.getToken());
      +   }
      +   ...
      +   ContainerManagementProtocolProxy nmPro = ContainerManagementProtocolProxy(conf, nmTokenCache);
      +   ...
      +   nmPro.startContainer(container, containerContext);
      +   ...
      + 
      +
    • +
    + It is also possible to mix the usage of a client ({@code AMRMClient} or + {@code NMClient}, or the async versions of them) with a protocol proxy + ({@code ContainerManagementProtocolProxy} or + {@code ApplicationMasterProtocol}).]]> +
    +
    + + + + + + + + + + + + + + The method to claim a resource with the SharedCacheManager. + The client uses a checksum to identify the resource and an + {@link ApplicationId} to identify which application will be using the + resource. +

    + +

    + The SharedCacheManager responds with whether or not the + resource exists in the cache. If the resource exists, a Path + to the resource in the shared cache is returned. If the resource does not + exist, null is returned instead. +

    + + @param applicationId ApplicationId of the application using the resource + @param resourceKey the key (i.e. checksum) that identifies the resource + @return Path to the resource, or null if it does not exist]]> +
    +
    + + + + + + + The method to release a resource with the SharedCacheManager. + This method is called once an application is no longer using a claimed + resource in the shared cache. The client uses a checksum to identify the + resource and an {@link ApplicationId} to identify which application is + releasing the resource. +

    + +

    + Note: This method is an optimization and the client is not required to call + it for correctness. +

    + + @param applicationId ApplicationId of the application releasing the + resource + @param resourceKey the key (i.e. checksum) that identifies the resource]]> +
    +
    + + + + + + + + + + +
    + + + + + + + + + + + + + + + + Obtain a {@link YarnClientApplication} for a new application, + which in turn contains the {@link ApplicationSubmissionContext} and + {@link org.apache.hadoop.yarn.api.protocolrecords.GetNewApplicationResponse} + objects. +

    + + @return {@link YarnClientApplication} built for a new application + @throws YarnException + @throws IOException]]> +
    +
    + + + + + + + Submit a new application to YARN. It is a blocking call - it + will not return {@link ApplicationId} until the submitted application is + submitted successfully and accepted by the ResourceManager. +

    + +

    + Users should provide an {@link ApplicationId} as part of the parameter + {@link ApplicationSubmissionContext} when submitting a new application, + otherwise it will throw the {@link ApplicationIdNotProvidedException}. +

    + +

    This internally calls {@link ApplicationClientProtocol#submitApplication + (SubmitApplicationRequest)}, and after that, it internally invokes + {@link ApplicationClientProtocol#getApplicationReport + (GetApplicationReportRequest)} and waits till it can make sure that the + application gets properly submitted. If RM fails over or RM restart + happens before ResourceManager saves the application's state, + {@link ApplicationClientProtocol + #getApplicationReport(GetApplicationReportRequest)} will throw + the {@link ApplicationNotFoundException}. This API automatically resubmits + the application with the same {@link ApplicationSubmissionContext} when it + catches the {@link ApplicationNotFoundException}

    + + @param appContext + {@link ApplicationSubmissionContext} containing all the details + needed to submit a new application + @return {@link ApplicationId} of the accepted application + @throws YarnException + @throws IOException + @see #createApplication()]]> +
    +
    + + + + + + + Fail an application attempt identified by given ID. +

    + + @param applicationAttemptId + {@link ApplicationAttemptId} of the attempt to fail. + @throws YarnException + in case of errors or if YARN rejects the request due to + access-control restrictions. + @throws IOException + @see #getQueueAclsInfo()]]> +
    +
    + + + + + + + Kill an application identified by given ID. +

    + + @param applicationId + {@link ApplicationId} of the application that needs to be killed + @throws YarnException + in case of errors or if YARN rejects the request due to + access-control restrictions. + @throws IOException + @see #getQueueAclsInfo()]]> +
    +
    + + + + + + + + Kill an application identified by given ID. +

    + @param applicationId {@link ApplicationId} of the application that needs to + be killed + @param diagnostics for killing an application. + @throws YarnException in case of errors or if YARN rejects the request due + to access-control restrictions. + @throws IOException]]> +
    +
    + + + + + + + Get a report of the given Application. +

    + +

    + In secure mode, YARN verifies access to the application, queue + etc. before accepting the request. +

    + +

    + If the user does not have VIEW_APP access then the following + fields in the report will be set to stubbed values: +

      +
    • host - set to "N/A"
    • +
    • RPC port - set to -1
    • +
    • client token - set to "N/A"
    • +
    • diagnostics - set to "N/A"
    • +
    • tracking URL - set to "N/A"
    • +
    • original tracking URL - set to "N/A"
    • +
    • resource usage report - all values are -1
    • +
    + + @param appId + {@link ApplicationId} of the application that needs a report + @return application report + @throws YarnException + @throws IOException]]> +
    +
    + + + + + + + The AMRM token is required for AM to RM scheduling operations. For + managed Application Masters Yarn takes care of injecting it. For unmanaged + Applications Masters, the token must be obtained via this method and set + in the {@link org.apache.hadoop.security.UserGroupInformation} of the + current user. +

    + The AMRM token will be returned only if all the following conditions are + met: +

      +
    • the requester is the owner of the ApplicationMaster
    • +
    • the application master is an unmanaged ApplicationMaster
    • +
    • the application master is in ACCEPTED state
    • +
    + Else this method returns NULL. + + @param appId {@link ApplicationId} of the application to get the AMRM token + @return the AMRM token if available + @throws YarnException + @throws IOException]]> +
    +
    + + + + + + Get a report (ApplicationReport) of all Applications in the cluster. +

    + +

    + If the user does not have VIEW_APP access for an application + then the corresponding report will be filtered as described in + {@link #getApplicationReport(ApplicationId)}. +

    + + @return a list of reports of all running applications + @throws YarnException + @throws IOException]]> +
    +
    + + + + + + + Get a report (ApplicationReport) of Applications + matching the given application types in the cluster. +

    + +

    + If the user does not have VIEW_APP access for an application + then the corresponding report will be filtered as described in + {@link #getApplicationReport(ApplicationId)}. +

    + + @param applicationTypes set of application types you are interested in + @return a list of reports of applications + @throws YarnException + @throws IOException]]> +
    +
    + + + + + + + Get a report (ApplicationReport) of Applications matching the given + application states in the cluster. +

    + +

    + If the user does not have VIEW_APP access for an application + then the corresponding report will be filtered as described in + {@link #getApplicationReport(ApplicationId)}. +

    + + @param applicationStates set of application states you are interested in + @return a list of reports of applications + @throws YarnException + @throws IOException]]> +
    +
    + + + + + + + + Get a report (ApplicationReport) of Applications matching the given + application types and application states in the cluster. +

    + +

    + If the user does not have VIEW_APP access for an application + then the corresponding report will be filtered as described in + {@link #getApplicationReport(ApplicationId)}. +

    + + @param applicationTypes set of application types you are interested in + @param applicationStates set of application states you are interested in + @return a list of reports of applications + @throws YarnException + @throws IOException]]> +
    +
    + + + + + + + + + + Get a report (ApplicationReport) of Applications matching the given users, + queues, application types and application states in the cluster. If any of + the params is set to null, it is not used when filtering. +

    + +

    + If the user does not have VIEW_APP access for an application + then the corresponding report will be filtered as described in + {@link #getApplicationReport(ApplicationId)}. +

    + + @param queues set of queues you are interested in + @param users set of users you are interested in + @param applicationTypes set of application types you are interested in + @param applicationStates set of application states you are interested in + @return a list of reports of applications + @throws YarnException + @throws IOException]]> +
    +
    + + + + + + Get metrics ({@link YarnClusterMetrics}) about the cluster. +

    + + @return cluster metrics + @throws YarnException + @throws IOException]]> +
    +
    + + + + + + + Get a report of nodes ({@link NodeReport}) in the cluster. +

    + + @param states The {@link NodeState}s to filter on. If no filter states are + given, nodes in all states will be returned. + @return A list of node reports + @throws YarnException + @throws IOException]]> +
    +
    + + + + + + + Get a delegation token so as to be able to talk to YARN using those tokens. + + @param renewer + Address of the renewer who can renew these tokens when needed by + securely talking to YARN. + @return a delegation token ({@link Token}) that can be used to + talk to YARN + @throws YarnException + @throws IOException]]> + + + + + + + + + Get information ({@link QueueInfo}) about a given queue. +

    + + @param queueName + Name of the queue whose information is needed + @return queue information + @throws YarnException + in case of errors or if YARN rejects the request due to + access-control restrictions. + @throws IOException]]> +
    +
    + + + + + + Get information ({@link QueueInfo}) about all queues, recursively if there + is a hierarchy +

    + + @return a list of queue-information for all queues + @throws YarnException + @throws IOException]]> +
    +
    + + + + + + Get information ({@link QueueInfo}) about top level queues. +

    + + @return a list of queue-information for all the top-level queues + @throws YarnException + @throws IOException]]> +
    +
    + + + + + + + Get information ({@link QueueInfo}) about all the immediate children queues + of the given queue +

    + + @param parent + Name of the queue whose child-queues' information is needed + @return a list of queue-information for all queues who are direct children + of the given parent queue. + @throws YarnException + @throws IOException]]> +
    +
    + + + + + + Get information about acls for current user on all the + existing queues. +

    + + @return a list of queue acls ({@link QueueUserACLInfo}) for + current user + @throws YarnException + @throws IOException]]> +
    +
    + + + + + + + Get a report of the given ApplicationAttempt. +

    + +

    + In secure mode, YARN verifies access to the application, queue + etc. before accepting the request. +

    + + @param applicationAttemptId + {@link ApplicationAttemptId} of the application attempt that needs + a report + @return application attempt report + @throws YarnException + @throws ApplicationAttemptNotFoundException if application attempt + not found + @throws IOException]]> +
    +
    + + + + + + + Get a report of all (ApplicationAttempts) of Application in the cluster. +

    + + @param applicationId application id of the app + @return a list of reports for all application attempts for specified + application. + @throws YarnException + @throws IOException]]> +
    +
    + + + + + + + Get a report of the given Container. +

    + +

    + In secure mode, YARN verifies access to the application, queue + etc. before accepting the request. +

    + + @param containerId + {@link ContainerId} of the container that needs a report + @return container report + @throws YarnException + @throws ContainerNotFoundException if container not found. + @throws IOException]]> +
    +
    + + + + + + + Get a report of all (Containers) of ApplicationAttempt in the cluster. +

    + + @param applicationAttemptId application attempt id + @return a list of reports of all containers for specified application + attempts + @throws YarnException + @throws IOException]]> +
    +
    + + + + + + + + Attempts to move the given application to the given queue. +

    + + @param appId + Application to move. + @param queue + Queue to place it in to. + @throws YarnException + @throws IOException]]> +
    +
    + + + + + + Obtain a {@link GetNewReservationResponse} for a new reservation, + which contains the {@link ReservationId} object. +

    + + @return The {@link GetNewReservationResponse} containing a new + {@link ReservationId} object. + @throws YarnException if reservation cannot be created. + @throws IOException if reservation cannot be created.]]> +
    +
    + + + + + + + The interface used by clients to submit a new reservation to the + {@code ResourceManager}. +

    + +

    + The client packages all details of its request in a + {@link ReservationSubmissionRequest} object. This contains information + about the amount of capacity, temporal constraints, and gang needs. + Furthermore, the reservation might be composed of multiple stages, with + ordering dependencies among them. +

    + +

    + In order to respond, a new admission control component in the + {@code ResourceManager} performs an analysis of the resources that have + been committed over the period of time the user is requesting, verify that + the user requests can be fulfilled, and that it respect a sharing policy + (e.g., {@code CapacityOverTimePolicy}). Once it has positively determined + that the ReservationRequest is satisfiable the {@code ResourceManager} + answers with a {@link ReservationSubmissionResponse} that includes a + {@link ReservationId}. Upon failure to find a valid allocation the response + is an exception with the message detailing the reason of failure. +

    + +

    + The semantics guarantees that the {@link ReservationId} returned, + corresponds to a valid reservation existing in the time-range request by + the user. The amount of capacity dedicated to such reservation can vary + overtime, depending of the allocation that has been determined. But it is + guaranteed to satisfy all the constraint expressed by the user in the + {@link ReservationDefinition} +

    + + @param request request to submit a new Reservation + @return response contains the {@link ReservationId} on accepting the + submission + @throws YarnException if the reservation cannot be created successfully + @throws IOException]]> +
    +
    + + + + + + + The interface used by clients to update an existing Reservation. This is + referred to as a re-negotiation process, in which a user that has + previously submitted a Reservation. +

    + +

    + The allocation is attempted by virtually substituting all previous + allocations related to this Reservation with new ones, that satisfy the new + {@link ReservationDefinition}. Upon success the previous allocation is + atomically substituted by the new one, and on failure (i.e., if the system + cannot find a valid allocation for the updated request), the previous + allocation remains valid. +

    + + @param request to update an existing Reservation (the + {@link ReservationUpdateRequest} should refer to an existing valid + {@link ReservationId}) + @return response empty on successfully updating the existing reservation + @throws YarnException if the request is invalid or reservation cannot be + updated successfully + @throws IOException]]> +
    +
    + + + + + + + The interface used by clients to remove an existing Reservation. +

    + + @param request to remove an existing Reservation (the + {@link ReservationDeleteRequest} should refer to an existing valid + {@link ReservationId}) + @return response empty on successfully deleting the existing reservation + @throws YarnException if the request is invalid or reservation cannot be + deleted successfully + @throws IOException]]> +
    +
    + + + + + + + The interface used by clients to get the list of reservations in a plan. + The reservationId will be used to search for reservations to list if it is + provided. Otherwise, it will select active reservations within the + startTime and endTime (inclusive). +

    + + @param request to list reservations in a plan. Contains fields to select + String queue, ReservationId reservationId, long startTime, + long endTime, and a bool includeReservationAllocations. + + queue: Required. Cannot be null or empty. Refers to the + reservable queue in the scheduler that was selected when + creating a reservation submission + {@link ReservationSubmissionRequest}. + + reservationId: Optional. If provided, other fields will + be ignored. + + startTime: Optional. If provided, only reservations that + end after the startTime will be selected. This defaults + to 0 if an invalid number is used. + + endTime: Optional. If provided, only reservations that + start on or before endTime will be selected. This defaults + to Long.MAX_VALUE if an invalid number is used. + + includeReservationAllocations: Optional. Flag that + determines whether the entire reservation allocations are + to be returned. Reservation allocations are subject to + change in the event of re-planning as described by + {@link ReservationDefinition}. + + @return response that contains information about reservations that are + being searched for. + @throws YarnException if the request is invalid + @throws IOException if the request failed otherwise]]> +
    +
    + + + + + + The interface used by client to get node to labels mappings in existing cluster +

    + + @return node to labels mappings + @throws YarnException + @throws IOException]]> +
    +
    + + + + + + The interface used by client to get labels to nodes mapping + in existing cluster +

    + + @return node to labels mappings + @throws YarnException + @throws IOException]]> +
    +
    + + + + + + + The interface used by client to get labels to nodes mapping + for specified labels in existing cluster +

    + + @param labels labels for which labels to nodes mapping has to be retrieved + @return labels to nodes mappings for specific labels + @throws YarnException + @throws IOException]]> +
    +
    + + + + + + The interface used by client to get node labels in the cluster +

    + + @return cluster node labels collection + @throws YarnException when there is a failure in + {@link ApplicationClientProtocol} + @throws IOException when there is a failure in + {@link ApplicationClientProtocol}]]> +
    +
    + + + + + + + + The interface used by client to set priority of an application +

    + @param applicationId + @param priority + @return updated priority of an application. + @throws YarnException + @throws IOException]]> +
    +
    + + + + + + + + Signal a container identified by given ID. +

    + + @param containerId + {@link ContainerId} of the container that needs to be signaled + @param command the signal container command + @throws YarnException + @throws IOException]]> +
    +
    +
    + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + Create a new instance of AMRMClientAsync.

    + + @param intervalMs heartbeat interval in milliseconds between AM and RM + @param callbackHandler callback handler that processes responses from + the ResourceManager]]> +
    +
    + + + + + + Create a new instance of AMRMClientAsync.

    + + @param client the AMRMClient instance + @param intervalMs heartbeat interval in milliseconds between AM and RM + @param callbackHandler callback handler that processes responses from + the ResourceManager]]> +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + allocate + @param req Resource request]]> + + + + + + + + + + + + + allocate. + Any previous pending resource change request of the same container will be + removed. + + Application that calls this method is expected to maintain the + Containers that are returned from previous successful + allocations or resource changes. By passing in the existing container and a + target resource capability to this method, the application requests the + ResourceManager to change the existing resource allocation to the target + resource allocation. + + @param container The container returned from the last successful resource + allocation or resource change + @param capability The target resource capability of the container]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + check to return true for each 1000 ms. + See also {@link #waitFor(com.google.common.base.Supplier, int)} + and {@link #waitFor(com.google.common.base.Supplier, int, int)} + @param check]]> + + + + + + + + check to return true for each + checkEveryMillis ms. + See also {@link #waitFor(com.google.common.base.Supplier, int, int)} + @param check user defined checker + @param checkEveryMillis interval to call check]]> + + + + + + + + + check to return true for each + checkEveryMillis ms. In the main loop, this method will log + the message "waiting in main loop" for each logInterval times + iteration to confirm the thread is alive. + @param check user defined checker + @param checkEveryMillis interval to call check + @param logInterval interval to log for each]]> + + + + + + + + + + AMRMClientAsync handles communication with the ResourceManager + and provides asynchronous updates on events such as container allocations and + completions. It contains a thread that sends periodic heartbeats to the + ResourceManager. + + It should be used by implementing a CallbackHandler: +
    + {@code
    + class MyCallbackHandler extends AMRMClientAsync.AbstractCallbackHandler {
    +   public void onContainersAllocated(List containers) {
    +     [run tasks on the containers]
    +   }
    +
    +   public void onContainersUpdated(List containers) {
    +     [determine if resource allocation of containers have been increased in
    +      the ResourceManager, and if so, inform the NodeManagers to increase the
    +      resource monitor/enforcement on the containers]
    +   }
    +
    +   public void onContainersCompleted(List statuses) {
    +     [update progress, check whether app is done]
    +   }
    +   
    +   public void onNodesUpdated(List updated) {}
    +   
    +   public void onReboot() {}
    + }
    + }
    + 
    + + The client's lifecycle should be managed similarly to the following: + +
    + {@code
    + AMRMClientAsync asyncClient = 
    +     createAMRMClientAsync(appAttId, 1000, new MyCallbackhandler());
    + asyncClient.init(conf);
    + asyncClient.start();
    + RegisterApplicationMasterResponse response = asyncClient
    +    .registerApplicationMaster(appMasterHostname, appMasterRpcPort,
    +       appMasterTrackingUrl);
    + asyncClient.addContainerRequest(containerRequest);
    + [... wait for application to complete]
    + asyncClient.unregisterApplicationMaster(status, appMsg, trackingUrl);
    + asyncClient.stop();
    + }
    + 
    ]]> +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NMClientAsync handles communication with all the NodeManagers + and provides asynchronous updates on getting responses from them. It + maintains a thread pool to communicate with individual NMs where a number of + worker threads process requests to NMs by using {@link NMClientImpl}. The max + size of the thread pool is configurable through + {@link YarnConfiguration#NM_CLIENT_ASYNC_THREAD_POOL_MAX_SIZE}. + + It should be used in conjunction with a CallbackHandler. For example + +
    + {@code
    + class MyCallbackHandler extends NMClientAsync.AbstractCallbackHandler {
    +   public void onContainerStarted(ContainerId containerId,
    +       Map allServiceResponse) {
    +     [post process after the container is started, process the response]
    +   }
    +
    +   public void onContainerResourceIncreased(ContainerId containerId,
    +       Resource resource) {
    +     [post process after the container resource is increased]
    +   }
    +
    +   public void onContainerStatusReceived(ContainerId containerId,
    +       ContainerStatus containerStatus) {
    +     [make use of the status of the container]
    +   }
    +
    +   public void onContainerStopped(ContainerId containerId) {
    +     [post process after the container is stopped]
    +   }
    +
    +   public void onStartContainerError(
    +       ContainerId containerId, Throwable t) {
    +     [handle the raised exception]
    +   }
    +
    +   public void onGetContainerStatusError(
    +       ContainerId containerId, Throwable t) {
    +     [handle the raised exception]
    +   }
    +
    +   public void onStopContainerError(
    +       ContainerId containerId, Throwable t) {
    +     [handle the raised exception]
    +   }
    + }
    + }
    + 
    + + The client's life-cycle should be managed like the following: + +
    + {@code
    + NMClientAsync asyncClient = 
    +     NMClientAsync.createNMClientAsync(new MyCallbackhandler());
    + asyncClient.init(conf);
    + asyncClient.start();
    + asyncClient.startContainer(container, containerLaunchContext);
    + [... wait for container being started]
    + asyncClient.getContainerStatus(container.getId(), container.getNodeId(),
    +     container.getContainerToken());
    + [... handle the status in the callback instance]
    + asyncClient.stopContainer(container.getId(), container.getNodeId(),
    +     container.getContainerToken());
    + [... wait for container being stopped]
    + asyncClient.stop();
    + }
    + 
    ]]> +
    +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    diff --git a/hadoop-yarn-project/hadoop-yarn/dev-support/jdiff/Apache_Hadoop_YARN_Common_2.8.0.xml b/hadoop-yarn-project/hadoop-yarn/dev-support/jdiff/Apache_Hadoop_YARN_Common_2.8.0.xml new file mode 100644 index 00000000000..a77dfd5d79c --- /dev/null +++ b/hadoop-yarn-project/hadoop-yarn/dev-support/jdiff/Apache_Hadoop_YARN_Common_2.8.0.xml @@ -0,0 +1,2665 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Type of proxy. + @return Proxy to the ResourceManager for the specified client protocol. + @throws IOException]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Type information of the proxy + @return Proxy to the RM + @throws IOException]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Send the information of a number of conceptual entities to the timeline + server. It is a blocking API. The method will not return until it gets the + response from the timeline server. +

    + + @param entities + the collection of {@link TimelineEntity} + @return the error information if the sent entities are not correctly stored + @throws IOException + @throws YarnException]]> +
    +
    + + + + + + + + + Send the information of a number of conceptual entities to the timeline + server. It is a blocking API. The method will not return until it gets the + response from the timeline server. + + This API is only for timeline service v1.5 +

    + + @param appAttemptId {@link ApplicationAttemptId} + @param groupId {@link TimelineEntityGroupId} + @param entities + the collection of {@link TimelineEntity} + @return the error information if the sent entities are not correctly stored + @throws IOException + @throws YarnException]]> +
    +
    + + + + + + + Send the information of a domain to the timeline server. It is a + blocking API. The method will not return until it gets the response from + the timeline server. +

    + + @param domain + an {@link TimelineDomain} object + @throws IOException + @throws YarnException]]> +
    +
    + + + + + + + + Send the information of a domain to the timeline server. It is a + blocking API. The method will not return until it gets the response from + the timeline server. + + This API is only for timeline service v1.5 +

    + + @param domain + an {@link TimelineDomain} object + @param appAttemptId {@link ApplicationAttemptId} + @throws IOException + @throws YarnException]]> +
    +
    + + + + + + + Get a delegation token so as to be able to talk to the timeline server in a + secure way. +

    + + @param renewer + Address of the renewer who can renew these tokens when needed by + securely talking to the timeline server + @return a delegation token ({@link Token}) that can be used to talk to the + timeline server + @throws IOException + @throws YarnException]]> +
    +
    + + + + + + + Renew a timeline delegation token. +

    + + @param timelineDT + the delegation token to renew + @return the new expiration time + @throws IOException + @throws YarnException]]> +
    +
    + + + + + + + Cancel a timeline delegation token. +

    + + @param timelineDT + the delegation token to cancel + @throws IOException + @throws YarnException]]> +
    +
    + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + parameterized event of type T]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + InputStream to be checksumed + @return the message digest of the input stream + @throws IOException]]> + + + + + + + + + + + + SharedCacheChecksum object based on the configurable + algorithm implementation + (see yarn.sharedcache.checksum.algo.impl) + + @return SharedCacheChecksum object]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The object type on which this state machine operates. + @param The state of the entity. + @param The external eventType to be handled. + @param The event object.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    diff --git a/hadoop-yarn-project/hadoop-yarn/dev-support/jdiff/Apache_Hadoop_YARN_Server_Common_2.8.0.xml b/hadoop-yarn-project/hadoop-yarn/dev-support/jdiff/Apache_Hadoop_YARN_Server_Common_2.8.0.xml new file mode 100644 index 00000000000..1634e2ee418 --- /dev/null +++ b/hadoop-yarn-project/hadoop-yarn/dev-support/jdiff/Apache_Hadoop_YARN_Server_Common_2.8.0.xml @@ -0,0 +1,829 @@ + + + + + + + + + + + + + + + + + + + + + + + + true if the node is healthy, else false]]> + + + + + diagnostic health report of the node. + @return diagnostic health report of the node]]> + + + + + last timestamp at which the health report was received. + @return last timestamp at which the health report was received]]> + + + + + It includes information such as: +
      +
    • + An indicator of whether the node is healthy, as determined by the + health-check script. +
    • +
    • The previous time at which the health status was reported.
    • +
    • A diagnostic report on the health status.
    • +
    + + @see NodeReport + @see ApplicationClientProtocol#getClusterNodes(org.apache.hadoop.yarn.api.protocolrecords.GetClusterNodesRequest)]]> +
    +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true if the iteration has more elements.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    From e7b7c96a30b0fee3af22a7719eaaa6481e76a963 Mon Sep 17 00:00:00 2001 From: Junping Du Date: Sat, 25 Mar 2017 14:30:38 -0700 Subject: [PATCH 134/188] Set jdiff.stable.api to 2.8.0 --- hadoop-project-dist/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hadoop-project-dist/pom.xml b/hadoop-project-dist/pom.xml index c83b2ffa59c..988d369b4b6 100644 --- a/hadoop-project-dist/pom.xml +++ b/hadoop-project-dist/pom.xml @@ -145,7 +145,7 @@ false - 2.7.2 + 2.8.0 -unstable From b63ae854f9f5a900ca860dcb26c30785af32bde0 Mon Sep 17 00:00:00 2001 From: Akira Ajisaka Date: Mon, 27 Mar 2017 11:23:41 +0900 Subject: [PATCH 135/188] YARN-6379. Remove unused argument in ClientRMService. Contributed by Kai Sasaki. --- .../yarn/server/resourcemanager/ClientRMService.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/ClientRMService.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/ClientRMService.java index 6130b33736d..8b28d6531c1 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/ClientRMService.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/ClientRMService.java @@ -1204,7 +1204,7 @@ public class ClientRMService extends AbstractService implements @Override public GetNewReservationResponse getNewReservation( GetNewReservationRequest request) throws YarnException, IOException { - checkReservationSystem(AuditConstants.CREATE_NEW_RESERVATION_REQUEST); + checkReservationSystem(); GetNewReservationResponse response = recordFactory.newRecordInstance(GetNewReservationResponse.class); @@ -1218,7 +1218,7 @@ public class ClientRMService extends AbstractService implements public ReservationSubmissionResponse submitReservation( ReservationSubmissionRequest request) throws YarnException, IOException { // Check if reservation system is enabled - checkReservationSystem(AuditConstants.SUBMIT_RESERVATION_REQUEST); + checkReservationSystem(); ReservationSubmissionResponse response = recordFactory.newRecordInstance(ReservationSubmissionResponse.class); ReservationId reservationId = request.getReservationId(); @@ -1277,7 +1277,7 @@ public class ClientRMService extends AbstractService implements public ReservationUpdateResponse updateReservation( ReservationUpdateRequest request) throws YarnException, IOException { // Check if reservation system is enabled - checkReservationSystem(AuditConstants.UPDATE_RESERVATION_REQUEST); + checkReservationSystem(); ReservationUpdateResponse response = recordFactory.newRecordInstance(ReservationUpdateResponse.class); // Validate the input @@ -1316,7 +1316,7 @@ public class ClientRMService extends AbstractService implements public ReservationDeleteResponse deleteReservation( ReservationDeleteRequest request) throws YarnException, IOException { // Check if reservation system is enabled - checkReservationSystem(AuditConstants.DELETE_RESERVATION_REQUEST); + checkReservationSystem(); ReservationDeleteResponse response = recordFactory.newRecordInstance(ReservationDeleteResponse.class); // Validate the input @@ -1355,7 +1355,7 @@ public class ClientRMService extends AbstractService implements public ReservationListResponse listReservations( ReservationListRequest requestInfo) throws YarnException, IOException { // Check if reservation system is enabled - checkReservationSystem(AuditConstants.LIST_RESERVATION_REQUEST); + checkReservationSystem(); ReservationListResponse response = recordFactory.newRecordInstance(ReservationListResponse.class); @@ -1418,7 +1418,7 @@ public class ClientRMService extends AbstractService implements labelsMgr.getClusterNodeLabels()); } - private void checkReservationSystem(String auditConstant) + private void checkReservationSystem() throws YarnException { // Check if reservation is enabled if (reservationSystem == null) { From e0a2eb71e3eb3efa865068089b649624132d33a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joe=20M=C3=A9sz=C3=A1ros?= Date: Fri, 24 Mar 2017 10:46:31 +0100 Subject: [PATCH 136/188] MAPREDUCE-6866. Fix getNumMapTasks() documentation in JobConf. This closes #205 Signed-off-by: Akira Ajisaka --- .../src/main/java/org/apache/hadoop/mapred/JobConf.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapred/JobConf.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapred/JobConf.java index ef9ec6168d7..f286a96a44d 100644 --- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapred/JobConf.java +++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapred/JobConf.java @@ -1286,10 +1286,10 @@ public class JobConf extends Configuration { } /** - * Get configured the number of reduce tasks for this job. + * Get the configured number of map tasks for this job. * Defaults to 1. * - * @return the number of reduce tasks for this job. + * @return the number of map tasks for this job. */ public int getNumMapTasks() { return getInt(JobContext.NUM_MAPS, 1); } @@ -1334,9 +1334,9 @@ public class JobConf extends Configuration { public void setNumMapTasks(int n) { setInt(JobContext.NUM_MAPS, n); } /** - * Get configured the number of reduce tasks for this job. Defaults to + * Get the configured number of reduce tasks for this job. Defaults to * 1. - * + * * @return the number of reduce tasks for this job. */ public int getNumReduceTasks() { return getInt(JobContext.NUM_REDUCES, 1); } From 945b006fe85473c1bb87830dbbfc1df9051c7bb9 Mon Sep 17 00:00:00 2001 From: Junping Du Date: Sun, 26 Mar 2017 23:18:05 -0700 Subject: [PATCH 137/188] MAPREDUCE-6868. License check for jdiff output files should be ignored. Contributed by Akira Ajisaka. --- hadoop-mapreduce-project/pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/hadoop-mapreduce-project/pom.xml b/hadoop-mapreduce-project/pom.xml index 829a56ca164..52c9c074e58 100644 --- a/hadoop-mapreduce-project/pom.xml +++ b/hadoop-mapreduce-project/pom.xml @@ -194,6 +194,7 @@ .eclipse.templates/ lib/jdiff/** + dev-support/jdiff/** From 96e2ab81444f7f87e0df2e1567ae7a26534cd1ab Mon Sep 17 00:00:00 2001 From: Yiqun Lin Date: Mon, 27 Mar 2017 19:23:37 +0800 Subject: [PATCH 138/188] HDFS-11555. Fix typos in class OfflineImageReconstructor. Contributed by Yiqun Lin. --- .../tools/offlineImageViewer/OfflineImageReconstructor.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/tools/offlineImageViewer/OfflineImageReconstructor.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/tools/offlineImageViewer/OfflineImageReconstructor.java index e80f4d43e68..d10b9246827 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/tools/offlineImageViewer/OfflineImageReconstructor.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/tools/offlineImageViewer/OfflineImageReconstructor.java @@ -1599,11 +1599,11 @@ class OfflineImageReconstructor { } catch (IOException e) { // Handle the case where does not exist. // Note: fsimage XML files which are missing are also missing - // many other fields that ovi needs to accurately reconstruct the + // many other fields that oiv needs to accurately reconstruct the // fsimage. throw new IOException("No section found at the top of " + "the fsimage XML. This XML file is too old to be processed " + - "by ovi.", e); + "by oiv.", e); } Node version = new Node(); loadNodeChildren(version, "version fields"); From 46d37a65cf09c2714b4c0c4ec0399031d60027a5 Mon Sep 17 00:00:00 2001 From: Wei-Chiu Chuang Date: Mon, 27 Mar 2017 05:23:45 -0700 Subject: [PATCH 139/188] HDFS-10506. Addendum patch to include missing changes. Contributed by Akira Ajisaka. --- .../tools/offlineImageViewer/OfflineImageReconstructor.java | 2 +- .../hdfs/tools/offlineImageViewer/PBImageXmlWriter.java | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/tools/offlineImageViewer/OfflineImageReconstructor.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/tools/offlineImageViewer/OfflineImageReconstructor.java index d10b9246827..2b5a19f4865 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/tools/offlineImageViewer/OfflineImageReconstructor.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/tools/offlineImageViewer/OfflineImageReconstructor.java @@ -669,7 +669,7 @@ class OfflineImageReconstructor { throw new IOException(" found without "); } blockBld.setBlockId(id); - Long genstamp = block.removeChildLong(INODE_SECTION_GEMSTAMP); + Long genstamp = block.removeChildLong(INODE_SECTION_GENSTAMP); if (genstamp == null) { throw new IOException(" found without "); } 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 index 5a42a6b7b02..681ebf6862c 100644 --- 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 @@ -119,7 +119,7 @@ public final class PBImageXmlWriter { public static final String INODE_SECTION_PERMISSION = "permission"; public static final String INODE_SECTION_BLOCKS = "blocks"; public static final String INODE_SECTION_BLOCK = "block"; - public static final String INODE_SECTION_GEMSTAMP = "genstamp"; + public static final String INODE_SECTION_GENSTAMP = "genstamp"; public static final String INODE_SECTION_NUM_BYTES = "numBytes"; public static final String INODE_SECTION_FILE_UNDER_CONSTRUCTION = "file-under-construction"; @@ -492,7 +492,7 @@ public final class PBImageXmlWriter { for (BlockProto b : f.getBlocksList()) { out.print("<" + INODE_SECTION_BLOCK + ">"); o(SECTION_ID, b.getBlockId()) - .o(INODE_SECTION_GEMSTAMP, b.getGenStamp()) + .o(INODE_SECTION_GENSTAMP, b.getGenStamp()) .o(INODE_SECTION_NUM_BYTES, b.getNumBytes()); out.print("\n"); } @@ -685,7 +685,7 @@ public final class PBImageXmlWriter { for (BlockProto b : f.getBlocksList()) { out.print("<" + INODE_SECTION_BLOCK + ">"); o(SECTION_ID, b.getBlockId()) - .o(INODE_SECTION_GEMSTAMP, b.getGenStamp()) + .o(INODE_SECTION_GENSTAMP, b.getGenStamp()) .o(INODE_SECTION_NUM_BYTES, b.getNumBytes()); out.print("\n"); } From 858d597be00ce006c1d1d4cd476729e4f1de8d25 Mon Sep 17 00:00:00 2001 From: Jonathan Eagles Date: Mon, 27 Mar 2017 11:01:48 -0500 Subject: [PATCH 140/188] Delay construction of PreCondition.check failure message in Configuration#set (jeagles) --- .../src/main/java/org/apache/hadoop/conf/Configuration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/conf/Configuration.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/conf/Configuration.java index a9c8d9cc934..60b03987265 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/conf/Configuration.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/conf/Configuration.java @@ -1208,7 +1208,7 @@ public class Configuration implements Iterable>, "Property name must not be null"); Preconditions.checkArgument( value != null, - "The value of property " + name + " must not be null"); + "The value of property %s must not be null", name); name = name.trim(); DeprecationContext deprecations = deprecationContext.get(); if (deprecations.getDeprecatedKeyMap().isEmpty()) { From db2adf356a937e4c539b2cf5dccfaf90271cf43e Mon Sep 17 00:00:00 2001 From: Jonathan Eagles Date: Mon, 27 Mar 2017 12:48:44 -0500 Subject: [PATCH 141/188] Configuration#get return value optimization (jeagles) --- .../org/apache/hadoop/conf/Configuration.java | 52 ++++++++++--------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/conf/Configuration.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/conf/Configuration.java index 60b03987265..ada8b0202e6 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/conf/Configuration.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/conf/Configuration.java @@ -619,42 +619,44 @@ public class Configuration implements Iterable>, * deprecated key, the value of the deprecated key is set as the value for * the provided property name. * + * @param deprecations deprecation context * @param name the property name * @return the first property in the list of properties mapping * the name or the name itself. */ private String[] handleDeprecation(DeprecationContext deprecations, - String name) { + String name) { if (null != name) { name = name.trim(); } - ArrayList names = new ArrayList(); - if (isDeprecated(name)) { - DeprecatedKeyInfo keyInfo = deprecations.getDeprecatedKeyMap().get(name); - if (keyInfo != null) { - if (!keyInfo.getAndSetAccessed()) { - logDeprecation(keyInfo.getWarningMessage(name)); - } - - for (String newKey : keyInfo.newKeys) { - if (newKey != null) { - names.add(newKey); - } + // Initialize the return value with requested name + String[] names = new String[]{name}; + // Deprecated keys are logged once and an updated names are returned + DeprecatedKeyInfo keyInfo = deprecations.getDeprecatedKeyMap().get(name); + if (keyInfo != null) { + if (!keyInfo.getAndSetAccessed()) { + logDeprecation(keyInfo.getWarningMessage(name)); + } + // Override return value for deprecated keys + names = keyInfo.newKeys; + } + // If there are no overlay values we can return early + Properties overlayProperties = getOverlay(); + if (overlayProperties.isEmpty()) { + return names; + } + // Update properties and overlays with reverse lookup values + for (String n : names) { + String deprecatedKey = deprecations.getReverseDeprecatedKeyMap().get(n); + if (deprecatedKey != null && !overlayProperties.containsKey(n)) { + String deprecatedValue = overlayProperties.getProperty(deprecatedKey); + if (deprecatedValue != null) { + getProps().setProperty(n, deprecatedValue); + overlayProperties.setProperty(n, deprecatedValue); } } } - if(names.size() == 0) { - names.add(name); - } - for(String n : names) { - String deprecatedKey = deprecations.getReverseDeprecatedKeyMap().get(n); - if (deprecatedKey != null && !getOverlay().containsKey(n) && - getOverlay().containsKey(deprecatedKey)) { - getProps().setProperty(n, getOverlay().getProperty(deprecatedKey)); - getOverlay().setProperty(n, getOverlay().getProperty(deprecatedKey)); - } - } - return names.toArray(new String[names.size()]); + return names; } private void handleDeprecation() { From cd014d57aa8b896da02b5bcadafbd404bca2bc12 Mon Sep 17 00:00:00 2001 From: Wangda Tan Date: Mon, 27 Mar 2017 13:29:09 -0700 Subject: [PATCH 142/188] YARN-6339. Improve performance for createAndGetApplicationReport. (Yunjiong Zhao via wangda) --- .../yarn/api/records/impl/pb/ProtoUtils.java | 6 ++-- .../resourcemanager/rmapp/RMAppImpl.java | 32 +++++++++++-------- .../TestRMAppLogAggregationStatus.java | 2 ++ 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/api/records/impl/pb/ProtoUtils.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/api/records/impl/pb/ProtoUtils.java index ab283e7c831..926c757f428 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/api/records/impl/pb/ProtoUtils.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/api/records/impl/pb/ProtoUtils.java @@ -296,6 +296,8 @@ public class ProtoUtils { * Log Aggregation Status */ private static final String LOG_AGGREGATION_STATUS_PREFIX = "LOG_"; + private static final int LOG_AGGREGATION_STATUS_PREFIX_LEN = + LOG_AGGREGATION_STATUS_PREFIX.length(); public static LogAggregationStatusProto convertToProtoFormat( LogAggregationStatus e) { return LogAggregationStatusProto.valueOf(LOG_AGGREGATION_STATUS_PREFIX @@ -304,8 +306,8 @@ public class ProtoUtils { public static LogAggregationStatus convertFromProtoFormat( LogAggregationStatusProto e) { - return LogAggregationStatus.valueOf(e.name().replace( - LOG_AGGREGATION_STATUS_PREFIX, "")); + return LogAggregationStatus.valueOf(e.name().substring( + LOG_AGGREGATION_STATUS_PREFIX_LEN)); } /* diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/rmapp/RMAppImpl.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/rmapp/RMAppImpl.java index 9f00b2e5fa4..f24908bd5e5 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/rmapp/RMAppImpl.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/rmapp/RMAppImpl.java @@ -34,6 +34,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TreeSet; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentSkipListSet; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; @@ -177,8 +178,8 @@ public class RMAppImpl implements RMApp, Recoverable { private long logAggregationStartTime = 0; private final long logAggregationStatusTimeout; private final Map logAggregationStatus = - new HashMap(); - private LogAggregationStatus logAggregationStatusForAppReport; + new ConcurrentHashMap(); + private volatile LogAggregationStatus logAggregationStatusForAppReport; private int logAggregationSucceed = 0; private int logAggregationFailed = 0; private Map> logAggregationDiagnosticsForNMs = @@ -1697,26 +1698,23 @@ public class RMAppImpl implements RMApp, Recoverable { public Map getLogAggregationReportsForApp() { try { this.readLock.lock(); - Map outputs = - new HashMap(); - outputs.putAll(logAggregationStatus); - if (!isLogAggregationFinished()) { - for (Entry output : outputs.entrySet()) { + if (!isLogAggregationFinished() && isAppInFinalState(this) && + System.currentTimeMillis() > this.logAggregationStartTime + + this.logAggregationStatusTimeout) { + for (Entry output : + logAggregationStatus.entrySet()) { if (!output.getValue().getLogAggregationStatus() .equals(LogAggregationStatus.TIME_OUT) && !output.getValue().getLogAggregationStatus() .equals(LogAggregationStatus.SUCCEEDED) && !output.getValue().getLogAggregationStatus() - .equals(LogAggregationStatus.FAILED) - && isAppInFinalState(this) - && System.currentTimeMillis() > this.logAggregationStartTime - + this.logAggregationStatusTimeout) { + .equals(LogAggregationStatus.FAILED)) { output.getValue().setLogAggregationStatus( LogAggregationStatus.TIME_OUT); } } } - return outputs; + return Collections.unmodifiableMap(logAggregationStatus); } finally { this.readLock.unlock(); } @@ -1824,11 +1822,17 @@ public class RMAppImpl implements RMApp, Recoverable { // the log aggregation is finished. And the log aggregation status will // not be updated anymore. if (logFailedCount > 0 && isAppInFinalState(this)) { + this.logAggregationStatusForAppReport = + LogAggregationStatus.FAILED; return LogAggregationStatus.FAILED; } else if (logTimeOutCount > 0) { + this.logAggregationStatusForAppReport = + LogAggregationStatus.TIME_OUT; return LogAggregationStatus.TIME_OUT; } if (isAppInFinalState(this)) { + this.logAggregationStatusForAppReport = + LogAggregationStatus.SUCCEEDED; return LogAggregationStatus.SUCCEEDED; } } else if (logRunningWithFailure > 0) { @@ -1844,7 +1848,9 @@ public class RMAppImpl implements RMApp, Recoverable { return this.logAggregationStatusForAppReport .equals(LogAggregationStatus.SUCCEEDED) || this.logAggregationStatusForAppReport - .equals(LogAggregationStatus.FAILED); + .equals(LogAggregationStatus.FAILED) + || this.logAggregationStatusForAppReport + .equals(LogAggregationStatus.TIME_OUT); } diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/logaggregationstatus/TestRMAppLogAggregationStatus.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/logaggregationstatus/TestRMAppLogAggregationStatus.java index 55a4eac4eea..677990b8c98 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/logaggregationstatus/TestRMAppLogAggregationStatus.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/logaggregationstatus/TestRMAppLogAggregationStatus.java @@ -413,6 +413,8 @@ public class TestRMAppLogAggregationStatus { Assert.assertEquals(LogAggregationStatus.TIME_OUT, rmApp.getLogAggregationStatusForAppReport()); + rmApp = (RMAppImpl)createRMApp(conf); + rmApp.handle(new RMAppEvent(rmApp.getApplicationId(), RMAppEventType.KILL)); // If the log aggregation status for all NMs are SUCCEEDED and Application // is at the final state, the log aggregation status for this app will // return SUCCEEDED From 64ea62c1ccc05d9b0a0030beafa60ddd31c38952 Mon Sep 17 00:00:00 2001 From: Masatake Iwasaki Date: Tue, 28 Mar 2017 09:13:44 +0900 Subject: [PATCH 143/188] HDFS-11486. Client close() should not fail fast if the last block is being decommissioned. Contributed by Wei-Chiu Chuang and Yiqun Lin. --- .../apache/hadoop/hdfs/TestDecommission.java | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDecommission.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDecommission.java index dc0edccdb1a..c2c6be12d1a 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDecommission.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDecommission.java @@ -694,6 +694,56 @@ public class TestDecommission extends AdminStatesBaseTest { assertEquals(dfs.getFileStatus(file).getLen(), writtenBytes); } + + @Test(timeout=120000) + public void testCloseWhileDecommission() throws IOException, + ExecutionException, InterruptedException { + LOG.info("Starting test testCloseWhileDecommission"); + + // min replication = 2 + getConf().setInt(DFSConfigKeys.DFS_NAMENODE_REPLICATION_MIN_KEY, 2); + startCluster(1, 3); + + FileSystem fileSys = getCluster().getFileSystem(0); + FSNamesystem ns = getCluster().getNamesystem(0); + + String openFile = "/testDecommissionWithOpenfile.dat"; + + writeFile(fileSys, new Path(openFile), (short)3); + // make sure the file was open for write + FSDataOutputStream fdos = fileSys.append(new Path(openFile)); + byte[] bytes = new byte[1]; + fdos.write(bytes); + fdos.hsync(); + + LocatedBlocks lbs = NameNodeAdapter.getBlockLocations( + getCluster().getNameNode(0), openFile, 0, fileSize); + + DatanodeInfo[] dnInfos4LastBlock = lbs.getLastLocatedBlock().getLocations(); + + ArrayList nodes = new ArrayList(); + ArrayList dnInfos = new ArrayList(); + + DatanodeManager dm = ns.getBlockManager().getDatanodeManager(); + //decommission 2 of the 3 nodes which have last block + nodes.add(dnInfos4LastBlock[0].getXferAddr()); + dnInfos.add(dm.getDatanode(dnInfos4LastBlock[0])); + nodes.add(dnInfos4LastBlock[1].getXferAddr()); + dnInfos.add(dm.getDatanode(dnInfos4LastBlock[1])); + + // because the cluster has only 3 nodes, and 2 of which are decomm'ed, + // the last block file will remain under replicated. + initExcludeHosts(nodes); + refreshNodes(0); + + // the close() should not fail despite the number of live replicas of + // the last block becomes one. + fdos.close(); + + // make sure the two datanodes remain in decomm in progress state + BlockManagerTestUtil.recheckDecommissionState(dm); + assertTrackedAndPending(dm.getDecomManager(), 2, 0); + } /** * Tests restart of namenode while datanode hosts are added to exclude file From 9bae6720cb8432efd78c909dc624c00e367cedf5 Mon Sep 17 00:00:00 2001 From: Robert Kanter Date: Mon, 27 Mar 2017 17:22:43 -0700 Subject: [PATCH 144/188] YARN-6050. AMs can't be scheduled on racks or nodes (rkanter) --- .../org/apache/hadoop/mapred/YARNRunner.java | 4 +- .../apache/hadoop/mapred/TestYARNRunner.java | 2 +- .../records/ApplicationSubmissionContext.java | 57 +++- .../src/main/proto/yarn_protos.proto | 2 +- .../ApplicationSubmissionContextPBImpl.java | 68 +++- .../nodelabels/CommonNodeLabelsManager.java | 22 ++ .../server/resourcemanager/RMAppManager.java | 104 ++++-- .../server/resourcemanager/RMServerUtils.java | 70 ++++- .../server/resourcemanager/rmapp/RMApp.java | 3 +- .../resourcemanager/rmapp/RMAppImpl.java | 22 +- .../rmapp/attempt/RMAppAttemptImpl.java | 33 +- .../scheduler/AbstractYarnScheduler.java | 5 + .../scheduler/ClusterNodeTracker.java | 65 +++- .../scheduler/ResourceScheduler.java | 9 + .../common/fica/FiCaSchedulerApp.java | 10 +- .../resourcemanager/webapp/dao/AppInfo.java | 2 +- .../yarn/server/resourcemanager/MockRM.java | 45 ++- .../resourcemanager/TestAppManager.java | 195 +++++++++++- .../resourcemanager/TestClientRMService.java | 8 +- .../TestNodeBlacklistingOnAMFailures.java | 184 +++++++++++ .../resourcemanager/TestRMServerUtils.java | 297 ++++++++++++++++++ .../applicationsmanager/MockAsm.java | 6 +- .../metrics/TestSystemMetricsPublisher.java | 3 +- .../resourcemanager/rmapp/MockRMApp.java | 11 +- .../rmapp/TestRMAppTransitions.java | 9 +- .../attempt/TestRMAppAttemptTransitions.java | 19 +- .../capacity/TestApplicationLimits.java | 4 +- .../TestApplicationLimitsByPartition.java | 4 +- .../capacity/TestCapacityScheduler.java | 2 +- .../scheduler/capacity/TestLeafQueue.java | 3 +- .../scheduler/fair/TestFairScheduler.java | 78 +++++ .../webapp/TestRMWebServicesApps.java | 10 +- 32 files changed, 1214 insertions(+), 142 deletions(-) create mode 100644 hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestRMServerUtils.java diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/main/java/org/apache/hadoop/mapred/YARNRunner.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/main/java/org/apache/hadoop/mapred/YARNRunner.java index 228c6af3b4c..2339c79f62b 100644 --- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/main/java/org/apache/hadoop/mapred/YARNRunner.java +++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/main/java/org/apache/hadoop/mapred/YARNRunner.java @@ -22,6 +22,7 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -589,7 +590,8 @@ public class YARNRunner implements ClientProtocol { amResourceRequest.setCapability(capability); amResourceRequest.setNumContainers(1); amResourceRequest.setNodeLabelExpression(amNodelabelExpression.trim()); - appContext.setAMContainerResourceRequest(amResourceRequest); + appContext.setAMContainerResourceRequests( + Collections.singletonList(amResourceRequest)); } // set labels for the Job containers appContext.setNodeLabelExpression(jobConf diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/java/org/apache/hadoop/mapred/TestYARNRunner.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/java/org/apache/hadoop/mapred/TestYARNRunner.java index 279c8cec853..c2bda6235a0 100644 --- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/java/org/apache/hadoop/mapred/TestYARNRunner.java +++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/java/org/apache/hadoop/mapred/TestYARNRunner.java @@ -571,7 +571,7 @@ public class TestYARNRunner { buildSubmitContext(yarnRunner, jobConf); assertEquals(appSubCtx.getNodeLabelExpression(), "GPU"); - assertEquals(appSubCtx.getAMContainerResourceRequest() + assertEquals(appSubCtx.getAMContainerResourceRequests().get(0) .getNodeLabelExpression(), "highMem"); } diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/api/records/ApplicationSubmissionContext.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/api/records/ApplicationSubmissionContext.java index e562aaae5c3..4f1d147791c 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/api/records/ApplicationSubmissionContext.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/api/records/ApplicationSubmissionContext.java @@ -18,6 +18,8 @@ package org.apache.hadoop.yarn.api.records; +import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.Set; @@ -100,7 +102,7 @@ public abstract class ApplicationSubmissionContext { amReq.setNumContainers(1); amReq.setRelaxLocality(true); amReq.setNodeLabelExpression(amContainerLabelExpression); - context.setAMContainerResourceRequest(amReq); + context.setAMContainerResourceRequests(Collections.singletonList(amReq)); return context; } @@ -159,7 +161,8 @@ public abstract class ApplicationSubmissionContext { context.setApplicationType(applicationType); context.setKeepContainersAcrossApplicationAttempts(keepContainers); context.setNodeLabelExpression(appLabelExpression); - context.setAMContainerResourceRequest(resourceRequest); + context.setAMContainerResourceRequests( + Collections.singletonList(resourceRequest)); return context; } @@ -454,29 +457,61 @@ public abstract class ApplicationSubmissionContext { public abstract void setNodeLabelExpression(String nodeLabelExpression); /** - * Get ResourceRequest of AM container, if this is not null, scheduler will - * use this to acquire resource for AM container. - * + * Get the ResourceRequest of the AM container. + * + * If this is not null, scheduler will use this to acquire resource for AM + * container. + * * If this is null, scheduler will assemble a ResourceRequest by using * getResource and getPriority of * ApplicationSubmissionContext. - * - * Number of containers and Priority will be ignore. - * - * @return ResourceRequest of AM container + * + * Number of containers and Priority will be ignored. + * + * @return ResourceRequest of the AM container + * @deprecated See {@link #getAMContainerResourceRequests()} */ @Public @Evolving + @Deprecated public abstract ResourceRequest getAMContainerResourceRequest(); /** - * Set ResourceRequest of AM container - * @param request of AM container + * Set ResourceRequest of the AM container + * @param request of the AM container + * @deprecated See {@link #setAMContainerResourceRequests(List)} */ @Public @Evolving + @Deprecated public abstract void setAMContainerResourceRequest(ResourceRequest request); + /** + * Get the ResourceRequests of the AM container. + * + * If this is not null, scheduler will use this to acquire resource for AM + * container. + * + * If this is null, scheduler will use the ResourceRequest as determined by + * getAMContainerResourceRequest and its behavior. + * + * Number of containers and Priority will be ignored. + * + * @return List of ResourceRequests of the AM container + */ + @Public + @Evolving + public abstract List getAMContainerResourceRequests(); + + /** + * Set ResourceRequests of the AM container. + * @param requests of the AM container + */ + @Public + @Evolving + public abstract void setAMContainerResourceRequests( + List requests); + /** * Get the attemptFailuresValidityInterval in milliseconds for the application * diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/proto/yarn_protos.proto b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/proto/yarn_protos.proto index 3b26a5cb3d6..587354a7a72 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/proto/yarn_protos.proto +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/proto/yarn_protos.proto @@ -378,7 +378,7 @@ message ApplicationSubmissionContextProto { optional LogAggregationContextProto log_aggregation_context = 14; optional ReservationIdProto reservation_id = 15; optional string node_label_expression = 16; - optional ResourceRequestProto am_container_resource_request = 17; + repeated ResourceRequestProto am_container_resource_request = 17; repeated ApplicationTimeoutMapProto application_timeouts = 18; } diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/api/records/impl/pb/ApplicationSubmissionContextPBImpl.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/api/records/impl/pb/ApplicationSubmissionContextPBImpl.java index 66930e74bf8..0148d0e4174 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/api/records/impl/pb/ApplicationSubmissionContextPBImpl.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/api/records/impl/pb/ApplicationSubmissionContextPBImpl.java @@ -18,6 +18,8 @@ package org.apache.hadoop.yarn.api.records.impl.pb; +import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -66,7 +68,7 @@ extends ApplicationSubmissionContext { private ContainerLaunchContext amContainer = null; private Resource resource = null; private Set applicationTags = null; - private ResourceRequest amResourceRequest = null; + private List amResourceRequests = null; private LogAggregationContext logAggregationContext = null; private ReservationId reservationId = null; private Map applicationTimeouts = null; @@ -127,9 +129,10 @@ extends ApplicationSubmissionContext { builder.clearApplicationTags(); builder.addAllApplicationTags(this.applicationTags); } - if (this.amResourceRequest != null) { - builder.setAmContainerResourceRequest( - convertToProtoFormat(this.amResourceRequest)); + if (this.amResourceRequests != null) { + builder.clearAmContainerResourceRequest(); + builder.addAllAmContainerResourceRequest( + convertToProtoFormat(this.amResourceRequests)); } if (this.logAggregationContext != null) { builder.setLogAggregationContext( @@ -430,13 +433,23 @@ extends ApplicationSubmissionContext { private PriorityProto convertToProtoFormat(Priority t) { return ((PriorityPBImpl)t).getProto(); } - - private ResourceRequestPBImpl convertFromProtoFormat(ResourceRequestProto p) { - return new ResourceRequestPBImpl(p); + + private List convertFromProtoFormat( + List ps) { + List rs = new ArrayList<>(); + for (ResourceRequestProto p : ps) { + rs.add(new ResourceRequestPBImpl(p)); + } + return rs; } - private ResourceRequestProto convertToProtoFormat(ResourceRequest t) { - return ((ResourceRequestPBImpl)t).getProto(); + private List convertToProtoFormat( + List ts) { + List rs = new ArrayList<>(ts.size()); + for (ResourceRequest t : ts) { + rs.add(((ResourceRequestPBImpl)t).getProto()); + } + return rs; } private ApplicationIdPBImpl convertFromProtoFormat(ApplicationIdProto p) { @@ -485,25 +498,46 @@ extends ApplicationSubmissionContext { } @Override + @Deprecated public ResourceRequest getAMContainerResourceRequest() { - ApplicationSubmissionContextProtoOrBuilder p = viaProto ? proto : builder; - if (this.amResourceRequest != null) { - return amResourceRequest; - } // Else via proto - if (!p.hasAmContainerResourceRequest()) { + List reqs = getAMContainerResourceRequests(); + if (reqs == null || reqs.isEmpty()) { return null; } - amResourceRequest = convertFromProtoFormat(p.getAmContainerResourceRequest()); - return amResourceRequest; + return getAMContainerResourceRequests().get(0); } @Override + public List getAMContainerResourceRequests() { + ApplicationSubmissionContextProtoOrBuilder p = viaProto ? proto : builder; + if (this.amResourceRequests != null) { + return amResourceRequests; + } // Else via proto + if (p.getAmContainerResourceRequestCount() == 0) { + return null; + } + amResourceRequests = + convertFromProtoFormat(p.getAmContainerResourceRequestList()); + return amResourceRequests; + } + + @Override + @Deprecated public void setAMContainerResourceRequest(ResourceRequest request) { maybeInitBuilder(); if (request == null) { builder.clearAmContainerResourceRequest(); } - this.amResourceRequest = request; + this.amResourceRequests = Collections.singletonList(request); + } + + @Override + public void setAMContainerResourceRequests(List requests) { + maybeInitBuilder(); + if (requests == null) { + builder.clearAmContainerResourceRequest(); + } + this.amResourceRequests = requests; } @Override diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/nodelabels/CommonNodeLabelsManager.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/nodelabels/CommonNodeLabelsManager.java index f3f4ba0ff90..60ade2dbcaa 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/nodelabels/CommonNodeLabelsManager.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/nodelabels/CommonNodeLabelsManager.java @@ -801,6 +801,28 @@ public class CommonNodeLabelsManager extends AbstractService { } } + /** + * Get nodes that have no labels. + * + * @return set of nodes with no labels + */ + public Set getNodesWithoutALabel() { + try { + readLock.lock(); + Set nodes = new HashSet<>(); + for (Host host : nodeCollections.values()) { + for (NodeId nodeId : host.nms.keySet()) { + if (getLabelsByNode(nodeId).isEmpty()) { + nodes.add(nodeId); + } + } + } + return Collections.unmodifiableSet(nodes); + } finally { + readLock.unlock(); + } + } + /** * Get mapping of labels to nodes for all the labels. diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/RMAppManager.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/RMAppManager.java index 1f446e6d48b..e0cff7bce66 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/RMAppManager.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/RMAppManager.java @@ -17,7 +17,9 @@ */ package org.apache.hadoop.yarn.server.resourcemanager; +import java.util.Collections; import java.util.LinkedList; +import java.util.List; import java.util.Map; import org.apache.commons.logging.Log; @@ -31,6 +33,8 @@ import org.apache.hadoop.yarn.api.records.ApplicationAccessType; import org.apache.hadoop.yarn.api.records.ApplicationId; import org.apache.hadoop.yarn.api.records.ApplicationSubmissionContext; import org.apache.hadoop.yarn.api.records.ApplicationTimeoutType; +import org.apache.hadoop.yarn.api.records.ExecutionType; +import org.apache.hadoop.yarn.api.records.ExecutionTypeRequest; import org.apache.hadoop.yarn.api.records.Priority; import org.apache.hadoop.yarn.api.records.QueueACL; import org.apache.hadoop.yarn.api.records.ResourceRequest; @@ -337,14 +341,16 @@ public class RMAppManager implements EventHandler, // has been disabled. Reject the recovery of this application if it // is true and give clear message so that user can react properly. if (!appContext.getUnmanagedAM() && - application.getAMResourceRequest() == null && + (application.getAMResourceRequests() == null || + application.getAMResourceRequests().isEmpty()) && !YarnConfiguration.areNodeLabelsEnabled(this.conf)) { // check application submission context and see if am resource request // or application itself contains any node label expression. - ResourceRequest amReqFromAppContext = - appContext.getAMContainerResourceRequest(); - String labelExp = (amReqFromAppContext != null) ? - amReqFromAppContext.getNodeLabelExpression() : null; + List amReqsFromAppContext = + appContext.getAMContainerResourceRequests(); + String labelExp = + (amReqsFromAppContext != null && !amReqsFromAppContext.isEmpty()) ? + amReqsFromAppContext.get(0).getNodeLabelExpression() : null; if (labelExp == null) { labelExp = appContext.getNodeLabelExpression(); } @@ -379,9 +385,9 @@ public class RMAppManager implements EventHandler, } ApplicationId applicationId = submissionContext.getApplicationId(); - ResourceRequest amReq = null; + List amReqs = null; try { - amReq = validateAndCreateResourceRequest(submissionContext, isRecovery); + amReqs = validateAndCreateResourceRequest(submissionContext, isRecovery); } catch (InvalidLabelResourceRequestException e) { // This can happen if the application had been submitted and run // with Node Label enabled but recover with Node Label disabled. @@ -444,7 +450,7 @@ public class RMAppManager implements EventHandler, submissionContext.getQueue(), submissionContext, this.scheduler, this.masterService, submitTime, submissionContext.getApplicationType(), - submissionContext.getApplicationTags(), amReq, startTime); + submissionContext.getApplicationTags(), amReqs, startTime); // Concurrent app submissions with same applicationId will fail here // Concurrent app submissions with different applicationIds will not // influence each other @@ -470,7 +476,7 @@ public class RMAppManager implements EventHandler, return application; } - private ResourceRequest validateAndCreateResourceRequest( + private List validateAndCreateResourceRequest( ApplicationSubmissionContext submissionContext, boolean isRecovery) throws InvalidResourceRequestException { // Validation of the ApplicationSubmissionContext needs to be completed @@ -480,33 +486,77 @@ public class RMAppManager implements EventHandler, // Check whether AM resource requirements are within required limits if (!submissionContext.getUnmanagedAM()) { - ResourceRequest amReq = submissionContext.getAMContainerResourceRequest(); - if (amReq == null) { - amReq = BuilderUtils - .newResourceRequest(RMAppAttemptImpl.AM_CONTAINER_PRIORITY, - ResourceRequest.ANY, submissionContext.getResource(), 1); - } - - // set label expression for AM container - if (null == amReq.getNodeLabelExpression()) { - amReq.setNodeLabelExpression(submissionContext - .getNodeLabelExpression()); + List amReqs = + submissionContext.getAMContainerResourceRequests(); + if (amReqs == null || amReqs.isEmpty()) { + if (submissionContext.getResource() != null) { + amReqs = Collections.singletonList(BuilderUtils + .newResourceRequest(RMAppAttemptImpl.AM_CONTAINER_PRIORITY, + ResourceRequest.ANY, submissionContext.getResource(), 1)); + } else { + throw new InvalidResourceRequestException("Invalid resource request, " + + "no resources requested"); + } } try { - SchedulerUtils.normalizeAndValidateRequest(amReq, - scheduler.getMaximumResourceCapability(), - submissionContext.getQueue(), scheduler, isRecovery, rmContext); + // Find the ANY request and ensure there's only one + ResourceRequest anyReq = null; + for (ResourceRequest amReq : amReqs) { + if (amReq.getResourceName().equals(ResourceRequest.ANY)) { + if (anyReq == null) { + anyReq = amReq; + } else { + throw new InvalidResourceRequestException("Invalid resource " + + "request, only one resource request with " + + ResourceRequest.ANY + " is allowed"); + } + } + } + if (anyReq == null) { + throw new InvalidResourceRequestException("Invalid resource request, " + + "no resource request specified with " + ResourceRequest.ANY); + } + + // Make sure that all of the requests agree with the ANY request + // and have correct values + for (ResourceRequest amReq : amReqs) { + amReq.setCapability(anyReq.getCapability()); + amReq.setExecutionTypeRequest( + ExecutionTypeRequest.newInstance(ExecutionType.GUARANTEED)); + amReq.setNumContainers(1); + amReq.setPriority(RMAppAttemptImpl.AM_CONTAINER_PRIORITY); + } + + // set label expression for AM ANY request if not set + if (null == anyReq.getNodeLabelExpression()) { + anyReq.setNodeLabelExpression(submissionContext + .getNodeLabelExpression()); + } + + // Put ANY request at the front + if (!amReqs.get(0).equals(anyReq)) { + amReqs.remove(anyReq); + amReqs.add(0, anyReq); + } + + // Normalize all requests + for (ResourceRequest amReq : amReqs) { + SchedulerUtils.normalizeAndValidateRequest(amReq, + scheduler.getMaximumResourceCapability(), + submissionContext.getQueue(), scheduler, isRecovery, rmContext); + + amReq.setCapability( + scheduler.getNormalizedResource(amReq.getCapability())); + } + return amReqs; } catch (InvalidResourceRequestException e) { LOG.warn("RM app submission failed in validating AM resource request" + " for application " + submissionContext.getApplicationId(), e); throw e; } - - amReq.setCapability(scheduler.getNormalizedResource(amReq.getCapability())); - return amReq; } - + return null; } diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/RMServerUtils.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/RMServerUtils.java index 0aa7a2c0ec9..35b0c983fac 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/RMServerUtils.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/RMServerUtils.java @@ -21,13 +21,16 @@ package org.apache.hadoop.yarn.server.resourcemanager; import java.io.IOException; import java.text.ParseException; import java.util.ArrayList; +import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; +import com.google.common.collect.Sets; import org.apache.commons.logging.Log; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.security.AccessControlException; @@ -41,6 +44,7 @@ import org.apache.hadoop.yarn.api.records.ApplicationTimeoutType; import org.apache.hadoop.yarn.api.records.ContainerId; import org.apache.hadoop.yarn.api.records.ContainerUpdateType; import org.apache.hadoop.yarn.api.records.ExecutionType; +import org.apache.hadoop.yarn.api.records.NodeId; import org.apache.hadoop.yarn.api.records.NodeState; import org.apache.hadoop.yarn.api.records.QueueInfo; import org.apache.hadoop.yarn.api.records.Resource; @@ -557,19 +561,65 @@ public class RMServerUtils { * * @param rmContext context * @param conf configuration - * @param amreq am resource request + * @param amReqs am resource requests * @return applicable node count */ public static int getApplicableNodeCountForAM(RMContext rmContext, - Configuration conf, ResourceRequest amreq) { - if (YarnConfiguration.areNodeLabelsEnabled(conf)) { - RMNodeLabelsManager labelManager = rmContext.getNodeLabelManager(); - String amNodeLabelExpression = amreq.getNodeLabelExpression(); - amNodeLabelExpression = (amNodeLabelExpression == null - || amNodeLabelExpression.trim().isEmpty()) - ? RMNodeLabelsManager.NO_LABEL : amNodeLabelExpression; - return labelManager.getActiveNMCountPerLabel(amNodeLabelExpression); + Configuration conf, List amReqs) { + // Determine the list of nodes that are eligible based on the strict + // resource requests + Set nodesForReqs = new HashSet<>(); + for (ResourceRequest amReq : amReqs) { + if (amReq.getRelaxLocality() && + !amReq.getResourceName().equals(ResourceRequest.ANY)) { + nodesForReqs.addAll( + rmContext.getScheduler().getNodeIds(amReq.getResourceName())); + } + } + + if (YarnConfiguration.areNodeLabelsEnabled(conf)) { + // Determine the list of nodes that are eligible based on the node label + String amNodeLabelExpression = amReqs.get(0).getNodeLabelExpression(); + Set nodesForLabels = + getNodeIdsForLabel(rmContext, amNodeLabelExpression); + if (nodesForLabels != null && !nodesForLabels.isEmpty()) { + // If only node labels, strip out any wildcard NodeIds and return + if (nodesForReqs.isEmpty()) { + for (Iterator it = nodesForLabels.iterator(); it.hasNext();) { + if (it.next().getPort() == 0) { + it.remove(); + } + } + return nodesForLabels.size(); + } else { + // The NodeIds common to both the strict resource requests and the + // node label is the eligible set + return Sets.intersection(nodesForReqs, nodesForLabels).size(); + } + } + } + + // If no strict resource request NodeIds nor node label NodeIds, then just + // return the entire cluster + if (nodesForReqs.isEmpty()) { + return rmContext.getScheduler().getNumClusterNodes(); + } + // No node label NodeIds, so return the strict resource request NodeIds + return nodesForReqs.size(); + } + + private static Set getNodeIdsForLabel(RMContext rmContext, + String label) { + label = (label == null || label.trim().isEmpty()) + ? RMNodeLabelsManager.NO_LABEL : label; + if (label.equals(RMNodeLabelsManager.NO_LABEL)) { + // NO_LABEL nodes aren't tracked directly + return rmContext.getNodeLabelManager().getNodesWithoutALabel(); + } else { + Map> labelsToNodes = + rmContext.getNodeLabelManager().getLabelsToNodes( + Collections.singleton(label)); + return labelsToNodes.get(label); } - return rmContext.getScheduler().getNumClusterNodes(); } } diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/rmapp/RMApp.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/rmapp/RMApp.java index b3a87a6e560..43fd1fbfbdf 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/rmapp/RMApp.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/rmapp/RMApp.java @@ -19,6 +19,7 @@ package org.apache.hadoop.yarn.server.resourcemanager.rmapp; import java.util.Collection; +import java.util.List; import java.util.Map; import java.util.Set; @@ -269,7 +270,7 @@ public interface RMApp extends EventHandler { ReservationId getReservationId(); - ResourceRequest getAMResourceRequest(); + List getAMResourceRequests(); Map getLogAggregationReportsForApp(); diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/rmapp/RMAppImpl.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/rmapp/RMAppImpl.java index f24908bd5e5..531844c11c3 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/rmapp/RMAppImpl.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/rmapp/RMAppImpl.java @@ -196,7 +196,7 @@ public class RMAppImpl implements RMApp, Recoverable { private RMAppEvent eventCausingFinalSaving; private RMAppState targetedFinalState; private RMAppState recoveredFinalState; - private ResourceRequest amReq; + private List amReqs; private CallerContext callerContext; @@ -424,10 +424,10 @@ public class RMAppImpl implements RMApp, Recoverable { ApplicationSubmissionContext submissionContext, YarnScheduler scheduler, ApplicationMasterService masterService, long submitTime, String applicationType, Set applicationTags, - ResourceRequest amReq) { + List amReqs) { this(applicationId, rmContext, config, name, user, queue, submissionContext, scheduler, masterService, submitTime, applicationType, applicationTags, - amReq, -1); + amReqs, -1); } public RMAppImpl(ApplicationId applicationId, RMContext rmContext, @@ -435,7 +435,7 @@ public class RMAppImpl implements RMApp, Recoverable { ApplicationSubmissionContext submissionContext, YarnScheduler scheduler, ApplicationMasterService masterService, long submitTime, String applicationType, Set applicationTags, - ResourceRequest amReq, long startTime) { + List amReqs, long startTime) { this.systemClock = SystemClock.getInstance(); @@ -458,7 +458,7 @@ public class RMAppImpl implements RMApp, Recoverable { } this.applicationType = applicationType; this.applicationTags = applicationTags; - this.amReq = amReq; + this.amReqs = amReqs; if (submissionContext.getPriority() != null) { this.applicationPriority = Priority .newInstance(submissionContext.getPriority().getPriority()); @@ -987,7 +987,7 @@ public class RMAppImpl implements RMApp, Recoverable { if (amBlacklistingEnabled && !submissionContext.getUnmanagedAM()) { currentAMBlacklistManager = new SimpleBlacklistManager( RMServerUtils.getApplicableNodeCountForAM(rmContext, conf, - getAMResourceRequest()), + getAMResourceRequests()), blacklistDisableThreshold); } else { currentAMBlacklistManager = new DisabledBlacklistManager(); @@ -995,7 +995,7 @@ public class RMAppImpl implements RMApp, Recoverable { } RMAppAttempt attempt = new RMAppAttemptImpl(appAttemptId, rmContext, scheduler, masterService, - submissionContext, conf, amReq, this, currentAMBlacklistManager); + submissionContext, conf, amReqs, this, currentAMBlacklistManager); attempts.put(appAttemptId, attempt); currentAttempt = attempt; } @@ -1690,8 +1690,8 @@ public class RMAppImpl implements RMApp, Recoverable { } @Override - public ResourceRequest getAMResourceRequest() { - return this.amReq; + public List getAMResourceRequests() { + return this.amReqs; } @Override @@ -1964,7 +1964,9 @@ public class RMAppImpl implements RMApp, Recoverable { public String getAmNodeLabelExpression() { String amNodeLabelExpression = null; if (!getApplicationSubmissionContext().getUnmanagedAM()) { - amNodeLabelExpression = getAMResourceRequest().getNodeLabelExpression(); + amNodeLabelExpression = + getAMResourceRequests() != null && !getAMResourceRequests().isEmpty() + ? getAMResourceRequests().get(0).getNodeLabelExpression() : null; amNodeLabelExpression = (amNodeLabelExpression == null) ? NodeLabel.NODE_LABEL_EXPRESSION_NOT_SET : amNodeLabelExpression; amNodeLabelExpression = (amNodeLabelExpression.trim().isEmpty()) diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/rmapp/attempt/RMAppAttemptImpl.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/rmapp/attempt/RMAppAttemptImpl.java index 5c0f48e5006..19503e519f2 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/rmapp/attempt/RMAppAttemptImpl.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/rmapp/attempt/RMAppAttemptImpl.java @@ -192,7 +192,7 @@ public class RMAppAttemptImpl implements RMAppAttempt, Recoverable { private Object transitionTodo; private RMAppAttemptMetrics attemptMetrics = null; - private ResourceRequest amReq = null; + private List amReqs = null; private BlacklistManager blacklistedNodesForAM = null; private String amLaunchDiagnostics; @@ -485,16 +485,16 @@ public class RMAppAttemptImpl implements RMAppAttempt, Recoverable { RMContext rmContext, YarnScheduler scheduler, ApplicationMasterService masterService, ApplicationSubmissionContext submissionContext, - Configuration conf, ResourceRequest amReq, RMApp rmApp) { + Configuration conf, List amReqs, RMApp rmApp) { this(appAttemptId, rmContext, scheduler, masterService, submissionContext, - conf, amReq, rmApp, new DisabledBlacklistManager()); + conf, amReqs, rmApp, new DisabledBlacklistManager()); } public RMAppAttemptImpl(ApplicationAttemptId appAttemptId, RMContext rmContext, YarnScheduler scheduler, ApplicationMasterService masterService, ApplicationSubmissionContext submissionContext, - Configuration conf, ResourceRequest amReq, RMApp rmApp, + Configuration conf, List amReqs, RMApp rmApp, BlacklistManager amBlacklistManager) { this.conf = conf; this.applicationAttemptId = appAttemptId; @@ -514,7 +514,7 @@ public class RMAppAttemptImpl implements RMAppAttempt, Recoverable { this.attemptMetrics = new RMAppAttemptMetrics(applicationAttemptId, rmContext); - this.amReq = amReq; + this.amReqs = amReqs; this.blacklistedNodesForAM = amBlacklistManager; final int diagnosticsLimitKC = getDiagnosticsLimitKCOrThrow(conf); @@ -1090,18 +1090,21 @@ public class RMAppAttemptImpl implements RMAppAttempt, Recoverable { // will be passed to scheduler, and scheduler will deduct the number after // AM container allocated - // Currently, following fields are all hard code, + // Currently, following fields are all hard coded, // TODO: change these fields when we want to support - // priority/resource-name/relax-locality specification for AM containers - // allocation. - appAttempt.amReq.setNumContainers(1); - appAttempt.amReq.setPriority(AM_CONTAINER_PRIORITY); - appAttempt.amReq.setResourceName(ResourceRequest.ANY); - appAttempt.amReq.setRelaxLocality(true); + // priority or multiple containers AM container allocation. + for (ResourceRequest amReq : appAttempt.amReqs) { + amReq.setNumContainers(1); + amReq.setPriority(AM_CONTAINER_PRIORITY); + } - appAttempt.getAMBlacklistManager().refreshNodeHostCount( + int numNodes = RMServerUtils.getApplicableNodeCountForAM(appAttempt.rmContext, - appAttempt.conf, appAttempt.amReq)); + appAttempt.conf, appAttempt.amReqs); + if (LOG.isDebugEnabled()) { + LOG.debug("Setting node count for blacklist to " + numNodes); + } + appAttempt.getAMBlacklistManager().refreshNodeHostCount(numNodes); ResourceBlacklistRequest amBlacklist = appAttempt.getAMBlacklistManager().getBlacklistUpdates(); @@ -1114,7 +1117,7 @@ public class RMAppAttemptImpl implements RMAppAttempt, Recoverable { Allocation amContainerAllocation = appAttempt.scheduler.allocate( appAttempt.applicationAttemptId, - Collections.singletonList(appAttempt.amReq), + appAttempt.amReqs, EMPTY_CONTAINER_RELEASE_LIST, amBlacklist.getBlacklistAdditions(), amBlacklist.getBlacklistRemovals(), diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/AbstractYarnScheduler.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/AbstractYarnScheduler.java index 213839dccc9..b954bdf0294 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/AbstractYarnScheduler.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/AbstractYarnScheduler.java @@ -1257,4 +1257,9 @@ public abstract class AbstractYarnScheduler rmContainer.getLastConfirmedResource(), null))); } } + + @Override + public List getNodeIds(String resourceName) { + return nodeTracker.getNodeIdsByResourceName(resourceName); + } } diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/ClusterNodeTracker.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/ClusterNodeTracker.java index e487f698d09..010e64506b6 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/ClusterNodeTracker.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/ClusterNodeTracker.java @@ -268,6 +268,9 @@ public class ClusterNodeTracker { /** * Convenience method to filter nodes based on a condition. + * + * @param nodeFilter A {@link NodeFilter} for filtering the nodes + * @return A list of filtered nodes */ public List getNodes(NodeFilter nodeFilter) { List nodeList = new ArrayList<>(); @@ -288,6 +291,37 @@ public class ClusterNodeTracker { return nodeList; } + public List getAllNodeIds() { + return getNodeIds(null); + } + + /** + * Convenience method to filter nodes based on a condition. + * + * @param nodeFilter A {@link NodeFilter} for filtering the nodes + * @return A list of filtered nodes + */ + public List getNodeIds(NodeFilter nodeFilter) { + List nodeList = new ArrayList<>(); + readLock.lock(); + try { + if (nodeFilter == null) { + for (N node : nodes.values()) { + nodeList.add(node.getNodeID()); + } + } else { + for (N node : nodes.values()) { + if (nodeFilter.accept(node)) { + nodeList.add(node.getNodeID()); + } + } + } + } finally { + readLock.unlock(); + } + return nodeList; + } + /** * Convenience method to sort nodes. * @@ -320,11 +354,38 @@ public class ClusterNodeTracker { resourceName != null && !resourceName.isEmpty()); List retNodes = new ArrayList<>(); if (ResourceRequest.ANY.equals(resourceName)) { - return getAllNodes(); + retNodes.addAll(getAllNodes()); } else if (nodeNameToNodeMap.containsKey(resourceName)) { retNodes.add(nodeNameToNodeMap.get(resourceName)); } else if (nodesPerRack.containsKey(resourceName)) { - return nodesPerRack.get(resourceName); + retNodes.addAll(nodesPerRack.get(resourceName)); + } else { + LOG.info( + "Could not find a node matching given resourceName " + resourceName); + } + return retNodes; + } + + /** + * Convenience method to return list of {@link NodeId} corresponding to + * resourceName passed in the {@link ResourceRequest}. + * + * @param resourceName Host/rack name of the resource, or + * {@link ResourceRequest#ANY} + * @return list of {@link NodeId} that match the resourceName + */ + public List getNodeIdsByResourceName(final String resourceName) { + Preconditions.checkArgument( + resourceName != null && !resourceName.isEmpty()); + List retNodes = new ArrayList<>(); + if (ResourceRequest.ANY.equals(resourceName)) { + retNodes.addAll(getAllNodeIds()); + } else if (nodeNameToNodeMap.containsKey(resourceName)) { + retNodes.add(nodeNameToNodeMap.get(resourceName).getNodeID()); + } else if (nodesPerRack.containsKey(resourceName)) { + for (N node : nodesPerRack.get(resourceName)) { + retNodes.add(node.getNodeID()); + } } else { LOG.info( "Could not find a node matching given resourceName " + resourceName); diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/ResourceScheduler.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/ResourceScheduler.java index 5649ccf7dca..d96d62545c8 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/ResourceScheduler.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/ResourceScheduler.java @@ -19,10 +19,12 @@ package org.apache.hadoop.yarn.server.resourcemanager.scheduler; import java.io.IOException; +import java.util.List; import org.apache.hadoop.classification.InterfaceAudience.LimitedPrivate; import org.apache.hadoop.classification.InterfaceStability.Evolving; import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.yarn.api.records.NodeId; import org.apache.hadoop.yarn.server.resourcemanager.RMContext; import org.apache.hadoop.yarn.server.resourcemanager.recovery.Recoverable; @@ -49,4 +51,11 @@ public interface ResourceScheduler extends YarnScheduler, Recoverable { * @throws IOException */ void reinitialize(Configuration conf, RMContext rmContext) throws IOException; + + /** + * Get the {@link NodeId} available in the cluster by resource name. + * @param resourceName resource name + * @return the number of available {@link NodeId} by resource name. + */ + List getNodeIds(String resourceName); } diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/common/fica/FiCaSchedulerApp.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/common/fica/FiCaSchedulerApp.java index fea29bb6a72..5c0b718e33c 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/common/fica/FiCaSchedulerApp.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/common/fica/FiCaSchedulerApp.java @@ -141,18 +141,20 @@ public class FiCaSchedulerApp extends SchedulerApplicationAttempt { Resource amResource; String partition; - if (rmApp == null || rmApp.getAMResourceRequest() == null) { + if (rmApp == null || rmApp.getAMResourceRequests() == null + || rmApp.getAMResourceRequests().isEmpty()) { // the rmApp may be undefined (the resource manager checks for this too) // and unmanaged applications do not provide an amResource request // in these cases, provide a default using the scheduler amResource = rmContext.getScheduler().getMinimumResourceCapability(); partition = CommonNodeLabelsManager.NO_LABEL; } else { - amResource = rmApp.getAMResourceRequest().getCapability(); + amResource = rmApp.getAMResourceRequests().get(0).getCapability(); partition = - (rmApp.getAMResourceRequest().getNodeLabelExpression() == null) + (rmApp.getAMResourceRequests().get(0) + .getNodeLabelExpression() == null) ? CommonNodeLabelsManager.NO_LABEL - : rmApp.getAMResourceRequest().getNodeLabelExpression(); + : rmApp.getAMResourceRequests().get(0).getNodeLabelExpression(); } setAppAMNodePartitionName(partition); diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/dao/AppInfo.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/dao/AppInfo.java index 4e85b671da7..10e627a4410 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/dao/AppInfo.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/dao/AppInfo.java @@ -229,7 +229,7 @@ public class AppInfo { appNodeLabelExpression = app.getApplicationSubmissionContext().getNodeLabelExpression(); amNodeLabelExpression = (unmanagedApplication) ? null - : app.getAMResourceRequest().getNodeLabelExpression(); + : app.getAMResourceRequests().get(0).getNodeLabelExpression(); // Setting partition based resource usage of application ResourceScheduler scheduler = rm.getRMContext().getScheduler(); diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/MockRM.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/MockRM.java index f9f42ad6e54..aca2fc5ec9c 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/MockRM.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/MockRM.java @@ -24,6 +24,7 @@ import java.security.PrivilegedAction; import java.security.PrivilegedExceptionAction; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.EnumSet; import java.util.List; import java.util.Map; @@ -678,6 +679,17 @@ public class MockRM extends ResourceManager { tokensConf); } + public RMApp submitApp(List amResourceRequests) + throws Exception { + return submitApp(amResourceRequests, "app1", + "user", null, false, null, + super.getConfig().getInt(YarnConfiguration.RM_AM_MAX_ATTEMPTS, + YarnConfiguration.DEFAULT_RM_AM_MAX_ATTEMPTS), null, null, true, + false, false, null, 0, null, true, + amResourceRequests.get(0).getPriority(), + amResourceRequests.get(0).getNodeLabelExpression(), null, null); + } + public RMApp submitApp(Resource capability, String name, String user, Map acls, boolean unmanaged, String queue, int maxAppAttempts, Credentials ts, String appType, @@ -688,6 +700,30 @@ public class MockRM extends ResourceManager { Map applicationTimeouts, ByteBuffer tokensConf) throws Exception { + priority = (priority == null) ? Priority.newInstance(0) : priority; + ResourceRequest amResourceRequest = ResourceRequest.newInstance( + priority, ResourceRequest.ANY, capability, 1); + if (amLabel != null && !amLabel.isEmpty()) { + amResourceRequest.setNodeLabelExpression(amLabel.trim()); + } + return submitApp(Collections.singletonList(amResourceRequest), name, user, + acls, unmanaged, queue, maxAppAttempts, ts, appType, waitForAccepted, + keepContainers, isAppIdProvided, applicationId, + attemptFailuresValidityInterval, logAggregationContext, + cancelTokensWhenComplete, priority, amLabel, applicationTimeouts, + tokensConf); + } + + public RMApp submitApp(List amResourceRequests, String name, + String user, Map acls, boolean unmanaged, + String queue, int maxAppAttempts, Credentials ts, String appType, + boolean waitForAccepted, boolean keepContainers, boolean isAppIdProvided, + ApplicationId applicationId, long attemptFailuresValidityInterval, + LogAggregationContext logAggregationContext, + boolean cancelTokensWhenComplete, Priority priority, String amLabel, + Map applicationTimeouts, + ByteBuffer tokensConf) + throws Exception { ApplicationId appId = isAppIdProvided ? applicationId : null; ApplicationClientProtocol client = getClientRMService(); if (! isAppIdProvided) { @@ -718,7 +754,6 @@ public class MockRM extends ResourceManager { sub.setApplicationType(appType); ContainerLaunchContext clc = Records .newRecord(ContainerLaunchContext.class); - sub.setResource(capability); clc.setApplicationACLs(acls); if (ts != null && UserGroupInformation.isSecurityEnabled()) { DataOutputBuffer dob = new DataOutputBuffer(); @@ -733,12 +768,12 @@ public class MockRM extends ResourceManager { sub.setLogAggregationContext(logAggregationContext); } sub.setCancelTokensWhenComplete(cancelTokensWhenComplete); - ResourceRequest amResourceRequest = ResourceRequest.newInstance( - Priority.newInstance(0), ResourceRequest.ANY, capability, 1); if (amLabel != null && !amLabel.isEmpty()) { - amResourceRequest.setNodeLabelExpression(amLabel.trim()); + for (ResourceRequest amResourceRequest : amResourceRequests) { + amResourceRequest.setNodeLabelExpression(amLabel.trim()); + } } - sub.setAMContainerResourceRequest(amResourceRequest); + sub.setAMContainerResourceRequests(amResourceRequests); req.setApplicationSubmissionContext(sub); UserGroupInformation fakeUser = UserGroupInformation.createUserForTesting(user, new String[] {"someGroup"}); diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestAppManager.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestAppManager.java index 892f8ba36ec..eb69efa207d 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestAppManager.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestAppManager.java @@ -31,6 +31,8 @@ import static org.mockito.Mockito.when; import java.io.IOException; import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.concurrent.ConcurrentMap; @@ -50,6 +52,8 @@ import org.apache.hadoop.yarn.api.records.ApplicationAccessType; import org.apache.hadoop.yarn.api.records.ApplicationId; import org.apache.hadoop.yarn.api.records.ApplicationSubmissionContext; import org.apache.hadoop.yarn.api.records.ContainerLaunchContext; +import org.apache.hadoop.yarn.api.records.ExecutionType; +import org.apache.hadoop.yarn.api.records.ExecutionTypeRequest; import org.apache.hadoop.yarn.api.records.Priority; import org.apache.hadoop.yarn.api.records.Resource; import org.apache.hadoop.yarn.api.records.ResourceRequest; @@ -57,11 +61,13 @@ import org.apache.hadoop.yarn.conf.YarnConfiguration; import org.apache.hadoop.yarn.event.AsyncDispatcher; import org.apache.hadoop.yarn.event.Dispatcher; import org.apache.hadoop.yarn.event.EventHandler; +import org.apache.hadoop.yarn.exceptions.InvalidResourceRequestException; import org.apache.hadoop.yarn.exceptions.YarnException; import org.apache.hadoop.yarn.factories.RecordFactory; import org.apache.hadoop.yarn.factory.providers.RecordFactoryProvider; import org.apache.hadoop.yarn.server.resourcemanager.ahs.RMApplicationHistoryWriter; import org.apache.hadoop.yarn.server.resourcemanager.metrics.SystemMetricsPublisher; +import org.apache.hadoop.yarn.server.resourcemanager.nodelabels.RMNodeLabelsManager; import org.apache.hadoop.yarn.server.resourcemanager.placement.PlacementManager; import org.apache.hadoop.yarn.server.resourcemanager.recovery.RMStateStore; import org.apache.hadoop.yarn.server.resourcemanager.rmapp.MockRMApp; @@ -72,6 +78,7 @@ import org.apache.hadoop.yarn.server.resourcemanager.rmapp.RMAppImpl; import org.apache.hadoop.yarn.server.resourcemanager.rmapp.RMAppMetrics; import org.apache.hadoop.yarn.server.resourcemanager.rmapp.RMAppState; import org.apache.hadoop.yarn.server.resourcemanager.rmapp.attempt.AMLivelinessMonitor; +import org.apache.hadoop.yarn.server.resourcemanager.rmapp.attempt.RMAppAttemptImpl; import org.apache.hadoop.yarn.server.resourcemanager.rmcontainer.ContainerAllocationExpirer; import org.apache.hadoop.yarn.server.resourcemanager.scheduler.ResourceScheduler; import org.apache.hadoop.yarn.server.resourcemanager.scheduler.YarnScheduler; @@ -312,7 +319,7 @@ public class TestAppManager{ ResourceRequest resReg = ResourceRequest.newInstance(Priority.newInstance(0), ResourceRequest.ANY, Resource.newInstance(1024, 1), 1); - sub.setAMContainerResourceRequest(resReg); + sub.setAMContainerResourceRequests(Collections.singletonList(resReg)); req.setApplicationSubmissionContext(sub); sub.setAMContainerSpec(mock(ContainerLaunchContext.class)); try { @@ -522,8 +529,157 @@ public class TestAppManager{ Assert.assertEquals("app event type is wrong before", RMAppEventType.KILL, appEventType); } + @SuppressWarnings("deprecation") @Test - public void testRMAppSubmit() throws Exception { + public void testRMAppSubmitAMContainerResourceRequests() throws Exception { + asContext.setResource(Resources.createResource(1024)); + asContext.setAMContainerResourceRequest( + ResourceRequest.newInstance(Priority.newInstance(0), + ResourceRequest.ANY, Resources.createResource(1024), 1, true)); + List reqs = new ArrayList<>(); + reqs.add(ResourceRequest.newInstance(Priority.newInstance(0), + ResourceRequest.ANY, Resources.createResource(1025), 1, false)); + reqs.add(ResourceRequest.newInstance(Priority.newInstance(0), + "/rack", Resources.createResource(1025), 1, false)); + reqs.add(ResourceRequest.newInstance(Priority.newInstance(0), + "/rack/node", Resources.createResource(1025), 1, true)); + asContext.setAMContainerResourceRequests(cloneResourceRequests(reqs)); + // getAMContainerResourceRequest uses the first entry of + // getAMContainerResourceRequests + Assert.assertEquals(reqs.get(0), asContext.getAMContainerResourceRequest()); + Assert.assertEquals(reqs, asContext.getAMContainerResourceRequests()); + RMApp app = testRMAppSubmit(); + for (ResourceRequest req : reqs) { + req.setNodeLabelExpression(RMNodeLabelsManager.NO_LABEL); + } + // setAMContainerResourceRequests has priority over + // setAMContainerResourceRequest and setResource + Assert.assertEquals(reqs, app.getAMResourceRequests()); + } + + @SuppressWarnings("deprecation") + @Test + public void testRMAppSubmitAMContainerResourceRequest() throws Exception { + asContext.setResource(Resources.createResource(1024)); + asContext.setAMContainerResourceRequests(null); + ResourceRequest req = + ResourceRequest.newInstance(Priority.newInstance(0), + ResourceRequest.ANY, Resources.createResource(1025), 1, true); + asContext.setAMContainerResourceRequest(cloneResourceRequest(req)); + // getAMContainerResourceRequests uses a singleton list of + // getAMContainerResourceRequest + Assert.assertEquals(req, asContext.getAMContainerResourceRequest()); + Assert.assertEquals(req, asContext.getAMContainerResourceRequests().get(0)); + Assert.assertEquals(1, asContext.getAMContainerResourceRequests().size()); + RMApp app = testRMAppSubmit(); + req.setNodeLabelExpression(RMNodeLabelsManager.NO_LABEL); + // setAMContainerResourceRequest has priority over setResource + Assert.assertEquals(Collections.singletonList(req), + app.getAMResourceRequests()); + } + + @Test + public void testRMAppSubmitResource() throws Exception { + asContext.setResource(Resources.createResource(1024)); + asContext.setAMContainerResourceRequests(null); + RMApp app = testRMAppSubmit(); + // setResource + Assert.assertEquals(Collections.singletonList( + ResourceRequest.newInstance(RMAppAttemptImpl.AM_CONTAINER_PRIORITY, + ResourceRequest.ANY, Resources.createResource(1024), 1, true, "")), + app.getAMResourceRequests()); + } + + @Test + public void testRMAppSubmitNoResourceRequests() throws Exception { + asContext.setResource(null); + asContext.setAMContainerResourceRequests(null); + try { + testRMAppSubmit(); + Assert.fail("Should have failed due to no ResourceRequest"); + } catch (InvalidResourceRequestException e) { + Assert.assertEquals( + "Invalid resource request, no resources requested", + e.getMessage()); + } + } + + @Test + public void testRMAppSubmitAMContainerResourceRequestsDisagree() + throws Exception { + asContext.setResource(null); + List reqs = new ArrayList<>(); + ResourceRequest anyReq = ResourceRequest.newInstance( + Priority.newInstance(1), + ResourceRequest.ANY, Resources.createResource(1024), 1, false, "label1", + ExecutionTypeRequest.newInstance(ExecutionType.GUARANTEED)); + reqs.add(anyReq); + reqs.add(ResourceRequest.newInstance(Priority.newInstance(2), + "/rack", Resources.createResource(1025), 2, false, "", + ExecutionTypeRequest.newInstance(ExecutionType.OPPORTUNISTIC))); + reqs.add(ResourceRequest.newInstance(Priority.newInstance(3), + "/rack/node", Resources.createResource(1026), 3, true, "", + ExecutionTypeRequest.newInstance(ExecutionType.OPPORTUNISTIC))); + asContext.setAMContainerResourceRequests(cloneResourceRequests(reqs)); + RMApp app = testRMAppSubmit(); + // It should force the requests to all agree on these points + for (ResourceRequest req : reqs) { + req.setCapability(anyReq.getCapability()); + req.setExecutionTypeRequest( + ExecutionTypeRequest.newInstance(ExecutionType.GUARANTEED)); + req.setNumContainers(1); + req.setPriority(Priority.newInstance(0)); + } + Assert.assertEquals(reqs, app.getAMResourceRequests()); + } + + @Test + public void testRMAppSubmitAMContainerResourceRequestsNoAny() + throws Exception { + asContext.setResource(null); + List reqs = new ArrayList<>(); + reqs.add(ResourceRequest.newInstance(Priority.newInstance(1), + "/rack", Resources.createResource(1025), 1, false)); + reqs.add(ResourceRequest.newInstance(Priority.newInstance(1), + "/rack/node", Resources.createResource(1025), 1, true)); + asContext.setAMContainerResourceRequests(cloneResourceRequests(reqs)); + // getAMContainerResourceRequest uses the first entry of + // getAMContainerResourceRequests + Assert.assertEquals(reqs, asContext.getAMContainerResourceRequests()); + try { + testRMAppSubmit(); + Assert.fail("Should have failed due to missing ANY ResourceRequest"); + } catch (InvalidResourceRequestException e) { + Assert.assertEquals( + "Invalid resource request, no resource request specified with *", + e.getMessage()); + } + } + + @Test + public void testRMAppSubmitAMContainerResourceRequestsTwoManyAny() + throws Exception { + asContext.setResource(null); + List reqs = new ArrayList<>(); + reqs.add(ResourceRequest.newInstance(Priority.newInstance(1), + ResourceRequest.ANY, Resources.createResource(1025), 1, false)); + reqs.add(ResourceRequest.newInstance(Priority.newInstance(1), + ResourceRequest.ANY, Resources.createResource(1025), 1, false)); + asContext.setAMContainerResourceRequests(cloneResourceRequests(reqs)); + // getAMContainerResourceRequest uses the first entry of + // getAMContainerResourceRequests + Assert.assertEquals(reqs, asContext.getAMContainerResourceRequests()); + try { + testRMAppSubmit(); + Assert.fail("Should have failed due to too many ANY ResourceRequests"); + } catch (InvalidResourceRequestException e) { + Assert.assertEquals( + "Invalid resource request, only one resource request with * is " + + "allowed", e.getMessage()); + } + } + + private RMApp testRMAppSubmit() throws Exception { appMonitor.submitApplication(asContext, "test"); RMApp app = rmContext.getRMApps().get(appId); Assert.assertNotNull("app is null", app); @@ -534,12 +690,14 @@ public class TestAppManager{ // wait for event to be processed int timeoutSecs = 0; - while ((getAppEventType() == RMAppEventType.KILL) && + while ((getAppEventType() == RMAppEventType.KILL) && timeoutSecs++ < 20) { Thread.sleep(1000); } Assert.assertEquals("app event type sent is wrong", RMAppEventType.START, getAppEventType()); + + return app; } @Test @@ -737,6 +895,15 @@ public class TestAppManager{ ResourceCalculator rs = mock(ResourceCalculator.class); when(scheduler.getResourceCalculator()).thenReturn(rs); + when(scheduler.getNormalizedResource(any())) + .thenAnswer(new Answer() { + @Override + public Resource answer(InvocationOnMock invocationOnMock) + throws Throwable { + return (Resource) invocationOnMock.getArguments()[0]; + } + }); + return scheduler; } @@ -753,4 +920,26 @@ public class TestAppManager{ YarnConfiguration.DEFAULT_RM_SCHEDULER_MINIMUM_ALLOCATION_MB); } + private static ResourceRequest cloneResourceRequest(ResourceRequest req) { + return ResourceRequest.newInstance( + Priority.newInstance(req.getPriority().getPriority()), + new String(req.getResourceName()), + Resource.newInstance(req.getCapability().getMemorySize(), + req.getCapability().getVirtualCores()), + req.getNumContainers(), + req.getRelaxLocality(), + req.getNodeLabelExpression() != null + ? new String(req.getNodeLabelExpression()) : null, + ExecutionTypeRequest.newInstance( + req.getExecutionTypeRequest().getExecutionType())); + } + + private static List cloneResourceRequests( + List reqs) { + List cloneReqs = new ArrayList<>(); + for (ResourceRequest req : reqs) { + cloneReqs.add(cloneResourceRequest(req)); + } + return cloneReqs; + } } diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestClientRMService.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestClientRMService.java index 7a67aa87594..f0e60a246f3 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestClientRMService.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestClientRMService.java @@ -38,6 +38,7 @@ import java.security.AccessControlException; import java.security.PrivilegedExceptionAction; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; @@ -1307,9 +1308,9 @@ public class TestClientRMService { spy(new RMAppImpl(applicationId3, rmContext, config, null, null, queueName, asContext, yarnScheduler, null, System.currentTimeMillis(), "YARN", null, - BuilderUtils.newResourceRequest( + Collections.singletonList(BuilderUtils.newResourceRequest( RMAppAttemptImpl.AM_CONTAINER_PRIORITY, ResourceRequest.ANY, - Resource.newInstance(1024, 1), 1)){ + Resource.newInstance(1024, 1), 1))){ @Override public ApplicationReport createAndGetApplicationReport( String clientUserName, boolean allowAccess) { @@ -1323,7 +1324,8 @@ public class TestClientRMService { return report; } }); - app.getAMResourceRequest().setNodeLabelExpression(amNodeLabelExpression); + app.getAMResourceRequests().get(0) + .setNodeLabelExpression(amNodeLabelExpression); ApplicationAttemptId attemptId = ApplicationAttemptId.newInstance( ApplicationId.newInstance(123456, 1), 1); RMAppAttemptImpl rmAppAttemptImpl = spy(new RMAppAttemptImpl(attemptId, diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestNodeBlacklistingOnAMFailures.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestNodeBlacklistingOnAMFailures.java index c80a799e7d8..b4adf480b35 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestNodeBlacklistingOnAMFailures.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestNodeBlacklistingOnAMFailures.java @@ -18,6 +18,7 @@ package org.apache.hadoop.yarn.server.resourcemanager; +import java.util.ArrayList; import java.util.List; import org.apache.hadoop.yarn.api.records.ApplicationAttemptId; @@ -28,6 +29,9 @@ import org.apache.hadoop.yarn.api.records.ContainerId; import org.apache.hadoop.yarn.api.records.ContainerState; import org.apache.hadoop.yarn.api.records.ContainerStatus; import org.apache.hadoop.yarn.api.records.NodeId; +import org.apache.hadoop.yarn.api.records.Priority; +import org.apache.hadoop.yarn.api.records.Resource; +import org.apache.hadoop.yarn.api.records.ResourceRequest; import org.apache.hadoop.yarn.conf.YarnConfiguration; import org.apache.hadoop.yarn.event.Dispatcher; import org.apache.hadoop.yarn.event.DrainDispatcher; @@ -156,6 +160,186 @@ public class TestNodeBlacklistingOnAMFailures { currentNode.getNodeId(), allocatedContainers.get(0).getNodeId()); } + @Test(timeout = 100000) + public void testNodeBlacklistingOnAMFailureStrictNodeLocality() + throws Exception { + YarnConfiguration conf = new YarnConfiguration(); + conf.setClass(YarnConfiguration.RM_SCHEDULER, CapacityScheduler.class, + ResourceScheduler.class); + conf.setBoolean(YarnConfiguration.AM_SCHEDULING_NODE_BLACKLISTING_ENABLED, + true); + + DrainDispatcher dispatcher = new DrainDispatcher(); + MockRM rm = startRM(conf, dispatcher); + CapacityScheduler scheduler = (CapacityScheduler) rm.getResourceScheduler(); + + // Register 5 nodes, so that we can blacklist atleast one if AM container + // is failed. As per calculation it will be like, 5nodes * 0.2 (default)=1. + MockNM nm1 = + new MockNM("127.0.0.1:1234", 8000, rm.getResourceTrackerService()); + nm1.registerNode(); + + MockNM nm2 = + new MockNM("127.0.0.2:2345", 8000, rm.getResourceTrackerService()); + nm2.registerNode(); + + MockNM nm3 = + new MockNM("127.0.0.3:2345", 8000, rm.getResourceTrackerService()); + nm3.registerNode(); + + MockNM nm4 = + new MockNM("127.0.0.4:2345", 8000, rm.getResourceTrackerService()); + nm4.registerNode(); + + MockNM nm5 = + new MockNM("127.0.0.5:2345", 8000, rm.getResourceTrackerService()); + nm5.registerNode(); + + // Specify a strict locality on nm2 + List reqs = new ArrayList<>(); + ResourceRequest nodeReq = ResourceRequest.newInstance( + Priority.newInstance(0), nm2.getNodeId().getHost(), + Resource.newInstance(200, 1), 1, true); + ResourceRequest rackReq = ResourceRequest.newInstance( + Priority.newInstance(0), "/default-rack", + Resource.newInstance(200, 1), 1, false); + ResourceRequest anyReq = ResourceRequest.newInstance( + Priority.newInstance(0), ResourceRequest.ANY, + Resource.newInstance(200, 1), 1, false); + reqs.add(anyReq); + reqs.add(rackReq); + reqs.add(nodeReq); + RMApp app = rm.submitApp(reqs); + + MockAM am1 = MockRM.launchAndRegisterAM(app, rm, nm2); + ContainerId amContainerId = + ContainerId.newContainerId(am1.getApplicationAttemptId(), 1); + RMContainer rmContainer = scheduler.getRMContainer(amContainerId); + NodeId nodeWhereAMRan = rmContainer.getAllocatedNode(); + Assert.assertEquals(nm2.getNodeId(), nodeWhereAMRan); + + // Set the exist status to INVALID so that we can verify that the system + // automatically blacklisting the node + makeAMContainerExit(rm, amContainerId, nm2, ContainerExitStatus.INVALID); + + // restart the am + RMAppAttempt attempt = MockRM.waitForAttemptScheduled(app, rm); + System.out.println("New AppAttempt launched " + attempt.getAppAttemptId()); + + nm2.nodeHeartbeat(true); + dispatcher.await(); + + // Now the AM container should be allocated + MockRM.waitForState(attempt, RMAppAttemptState.ALLOCATED, 20000); + + MockAM am2 = rm.sendAMLaunched(attempt.getAppAttemptId()); + rm.waitForState(attempt.getAppAttemptId(), RMAppAttemptState.LAUNCHED); + amContainerId = + ContainerId.newContainerId(am2.getApplicationAttemptId(), 1); + rmContainer = scheduler.getRMContainer(amContainerId); + nodeWhereAMRan = rmContainer.getAllocatedNode(); + + // The second AM should be on the same node because the strict locality + // made the eligible nodes only 1, so the blacklisting threshold kicked in + System.out.println("AM ran on " + nodeWhereAMRan); + Assert.assertEquals(nm2.getNodeId(), nodeWhereAMRan); + + am2.registerAppAttempt(); + rm.waitForState(app.getApplicationId(), RMAppState.RUNNING); + } + + @Test(timeout = 100000) + public void testNodeBlacklistingOnAMFailureRelaxedNodeLocality() + throws Exception { + YarnConfiguration conf = new YarnConfiguration(); + conf.setClass(YarnConfiguration.RM_SCHEDULER, CapacityScheduler.class, + ResourceScheduler.class); + conf.setBoolean(YarnConfiguration.AM_SCHEDULING_NODE_BLACKLISTING_ENABLED, + true); + + DrainDispatcher dispatcher = new DrainDispatcher(); + MockRM rm = startRM(conf, dispatcher); + CapacityScheduler scheduler = (CapacityScheduler) rm.getResourceScheduler(); + + // Register 5 nodes, so that we can blacklist atleast one if AM container + // is failed. As per calculation it will be like, 5nodes * 0.2 (default)=1. + MockNM nm1 = + new MockNM("127.0.0.1:1234", 8000, rm.getResourceTrackerService()); + nm1.registerNode(); + + MockNM nm2 = + new MockNM("127.0.0.2:2345", 8000, rm.getResourceTrackerService()); + nm2.registerNode(); + + MockNM nm3 = + new MockNM("127.0.0.3:2345", 8000, rm.getResourceTrackerService()); + nm3.registerNode(); + + MockNM nm4 = + new MockNM("127.0.0.4:2345", 8000, rm.getResourceTrackerService()); + nm4.registerNode(); + + MockNM nm5 = + new MockNM("127.0.0.5:2345", 8000, rm.getResourceTrackerService()); + nm5.registerNode(); + + // Specify a relaxed locality on nm2 + List reqs = new ArrayList<>(); + ResourceRequest nodeReq = ResourceRequest.newInstance( + Priority.newInstance(0), nm2.getNodeId().getHost(), + Resource.newInstance(200, 1), 1, true); + ResourceRequest rackReq = ResourceRequest.newInstance( + Priority.newInstance(0), "/default-rack", + Resource.newInstance(200, 1), 1, true); + ResourceRequest anyReq = ResourceRequest.newInstance( + Priority.newInstance(0), ResourceRequest.ANY, + Resource.newInstance(200, 1), 1, true); + reqs.add(anyReq); + reqs.add(rackReq); + reqs.add(nodeReq); + RMApp app = rm.submitApp(reqs); + + MockAM am1 = MockRM.launchAndRegisterAM(app, rm, nm2); + ContainerId amContainerId = + ContainerId.newContainerId(am1.getApplicationAttemptId(), 1); + RMContainer rmContainer = scheduler.getRMContainer(amContainerId); + NodeId nodeWhereAMRan = rmContainer.getAllocatedNode(); + Assert.assertEquals(nm2.getNodeId(), nodeWhereAMRan); + + // Set the exist status to INVALID so that we can verify that the system + // automatically blacklisting the node + makeAMContainerExit(rm, amContainerId, nm2, ContainerExitStatus.INVALID); + + // restart the am + RMAppAttempt attempt = MockRM.waitForAttemptScheduled(app, rm); + System.out.println("New AppAttempt launched " + attempt.getAppAttemptId()); + + nm2.nodeHeartbeat(true); + nm1.nodeHeartbeat(true); + nm3.nodeHeartbeat(true); + nm4.nodeHeartbeat(true); + nm5.nodeHeartbeat(true); + dispatcher.await(); + + // Now the AM container should be allocated + MockRM.waitForState(attempt, RMAppAttemptState.ALLOCATED, 20000); + + MockAM am2 = rm.sendAMLaunched(attempt.getAppAttemptId()); + rm.waitForState(attempt.getAppAttemptId(), RMAppAttemptState.LAUNCHED); + amContainerId = + ContainerId.newContainerId(am2.getApplicationAttemptId(), 1); + rmContainer = scheduler.getRMContainer(amContainerId); + nodeWhereAMRan = rmContainer.getAllocatedNode(); + + // The second AM should be on a different node because the relaxed locality + // made the app schedulable on other nodes and nm2 is blacklisted + System.out.println("AM ran on " + nodeWhereAMRan); + Assert.assertNotEquals(nm2.getNodeId(), nodeWhereAMRan); + + am2.registerAppAttempt(); + rm.waitForState(app.getApplicationId(), RMAppState.RUNNING); + } + @Test(timeout = 100000) public void testNoBlacklistingForNonSystemErrors() throws Exception { diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestRMServerUtils.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestRMServerUtils.java new file mode 100644 index 00000000000..078b8fd3290 --- /dev/null +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestRMServerUtils.java @@ -0,0 +1,297 @@ +/** + * 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.yarn.server.resourcemanager; + +import org.apache.hadoop.yarn.api.records.NodeId; +import org.apache.hadoop.yarn.api.records.Priority; +import org.apache.hadoop.yarn.api.records.Resource; +import org.apache.hadoop.yarn.api.records.ResourceRequest; +import org.apache.hadoop.yarn.conf.YarnConfiguration; +import org.apache.hadoop.yarn.server.resourcemanager.nodelabels.RMNodeLabelsManager; +import org.apache.hadoop.yarn.server.resourcemanager.scheduler.ResourceScheduler; +import org.junit.Assert; +import org.junit.Test; +import org.mockito.Mockito; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class TestRMServerUtils { + @Test + public void testGetApplicableNodeCountForAMLocality() throws Exception { + List rack1Nodes = new ArrayList<>(); + for (int i = 0; i < 29; i++) { + rack1Nodes.add(NodeId.newInstance("host" + i, 1234)); + } + NodeId node1 = NodeId.newInstance("node1", 1234); + NodeId node2 = NodeId.newInstance("node2", 1234); + rack1Nodes.add(node2); + + YarnConfiguration conf = new YarnConfiguration(); + conf.setBoolean(YarnConfiguration.NODE_LABELS_ENABLED, false); + ResourceScheduler scheduler = Mockito.mock(ResourceScheduler.class); + Mockito.when(scheduler.getNumClusterNodes()).thenReturn(100); + Mockito.when(scheduler.getNodeIds("/rack1")).thenReturn(rack1Nodes); + Mockito.when(scheduler.getNodeIds("node1")) + .thenReturn(Collections.singletonList(node1)); + Mockito.when(scheduler.getNodeIds("node2")) + .thenReturn(Collections.singletonList(node2)); + RMContext rmContext = Mockito.mock(RMContext.class); + Mockito.when(rmContext.getScheduler()).thenReturn(scheduler); + + ResourceRequest anyReq = createResourceRequest(ResourceRequest.ANY, + true, null); + List reqs = new ArrayList<>(); + reqs.add(anyReq); + Assert.assertEquals(100, + RMServerUtils.getApplicableNodeCountForAM(rmContext, conf, reqs)); + + ResourceRequest rackReq = createResourceRequest("/rack1", true, null); + reqs.add(rackReq); + Assert.assertEquals(30, + RMServerUtils.getApplicableNodeCountForAM(rmContext, conf, reqs)); + anyReq.setRelaxLocality(false); + Assert.assertEquals(30, + RMServerUtils.getApplicableNodeCountForAM(rmContext, conf, reqs)); + rackReq.setRelaxLocality(false); + Assert.assertEquals(100, + RMServerUtils.getApplicableNodeCountForAM(rmContext, conf, reqs)); + + ResourceRequest node1Req = createResourceRequest("node1", false, null); + reqs.add(node1Req); + Assert.assertEquals(100, + RMServerUtils.getApplicableNodeCountForAM(rmContext, conf, reqs)); + node1Req.setRelaxLocality(true); + Assert.assertEquals(1, + RMServerUtils.getApplicableNodeCountForAM(rmContext, conf, reqs)); + rackReq.setRelaxLocality(true); + Assert.assertEquals(31, + RMServerUtils.getApplicableNodeCountForAM(rmContext, conf, reqs)); + + ResourceRequest node2Req = createResourceRequest("node2", false, null); + reqs.add(node2Req); + Assert.assertEquals(31, + RMServerUtils.getApplicableNodeCountForAM(rmContext, conf, reqs)); + node2Req.setRelaxLocality(true); + Assert.assertEquals(31, + RMServerUtils.getApplicableNodeCountForAM(rmContext, conf, reqs)); + rackReq.setRelaxLocality(false); + Assert.assertEquals(2, + RMServerUtils.getApplicableNodeCountForAM(rmContext, conf, reqs)); + node1Req.setRelaxLocality(false); + Assert.assertEquals(1, + RMServerUtils.getApplicableNodeCountForAM(rmContext, conf, reqs)); + node2Req.setRelaxLocality(false); + Assert.assertEquals(100, + RMServerUtils.getApplicableNodeCountForAM(rmContext, conf, reqs)); + } + + @Test + public void testGetApplicableNodeCountForAMLabels() throws Exception { + Set noLabelNodes = new HashSet<>(); + for (int i = 0; i < 80; i++) { + noLabelNodes.add(NodeId.newInstance("host" + i, 1234)); + } + Set label1Nodes = new HashSet<>(); + for (int i = 80; i < 90; i++) { + label1Nodes.add(NodeId.newInstance("host" + i, 1234)); + } + label1Nodes.add(NodeId.newInstance("host101", 0)); + label1Nodes.add(NodeId.newInstance("host102", 0)); + Map> label1NodesMap = new HashMap<>(); + label1NodesMap.put("label1", label1Nodes); + + YarnConfiguration conf = new YarnConfiguration(); + conf.setBoolean(YarnConfiguration.NODE_LABELS_ENABLED, true); + ResourceScheduler scheduler = Mockito.mock(ResourceScheduler.class); + Mockito.when(scheduler.getNumClusterNodes()).thenReturn(100); + RMContext rmContext = Mockito.mock(RMContext.class); + Mockito.when(rmContext.getScheduler()).thenReturn(scheduler); + RMNodeLabelsManager labMan = Mockito.mock(RMNodeLabelsManager.class); + Mockito.when(labMan.getNodesWithoutALabel()).thenReturn(noLabelNodes); + Mockito.when(labMan.getLabelsToNodes(Collections.singleton("label1"))) + .thenReturn(label1NodesMap); + Mockito.when(rmContext.getNodeLabelManager()).thenReturn(labMan); + + ResourceRequest anyReq = createResourceRequest(ResourceRequest.ANY, + true, null); + List reqs = new ArrayList<>(); + reqs.add(anyReq); + Assert.assertEquals(80, + RMServerUtils.getApplicableNodeCountForAM(rmContext, conf, reqs)); + anyReq.setNodeLabelExpression("label1"); + Assert.assertEquals(10, + RMServerUtils.getApplicableNodeCountForAM(rmContext, conf, reqs)); + } + + @Test + public void testGetApplicableNodeCountForAMLocalityAndLabels() + throws Exception { + List rack1Nodes = new ArrayList<>(); + for (int i = 0; i < 29; i++) { + rack1Nodes.add(NodeId.newInstance("host" + i, 1234)); + } + NodeId node1 = NodeId.newInstance("node1", 1234); + NodeId node2 = NodeId.newInstance("node2", 1234); + rack1Nodes.add(node2); + Set noLabelNodes = new HashSet<>(); + for (int i = 0; i < 19; i++) { + noLabelNodes.add(rack1Nodes.get(i)); + } + noLabelNodes.add(node2); + for (int i = 29; i < 89; i++) { + noLabelNodes.add(NodeId.newInstance("host" + i, 1234)); + } + Set label1Nodes = new HashSet<>(); + label1Nodes.add(node1); + for (int i = 89; i < 93; i++) { + label1Nodes.add(NodeId.newInstance("host" + i, 1234)); + } + for (int i = 19; i < 29; i++) { + label1Nodes.add(rack1Nodes.get(i)); + } + label1Nodes.add(NodeId.newInstance("host101", 0)); + label1Nodes.add(NodeId.newInstance("host102", 0)); + Map> label1NodesMap = new HashMap<>(); + label1NodesMap.put("label1", label1Nodes); + + YarnConfiguration conf = new YarnConfiguration(); + conf.setBoolean(YarnConfiguration.NODE_LABELS_ENABLED, true); + ResourceScheduler scheduler = Mockito.mock(ResourceScheduler.class); + Mockito.when(scheduler.getNumClusterNodes()).thenReturn(100); + Mockito.when(scheduler.getNodeIds("/rack1")).thenReturn(rack1Nodes); + Mockito.when(scheduler.getNodeIds("node1")) + .thenReturn(Collections.singletonList(node1)); + Mockito.when(scheduler.getNodeIds("node2")) + .thenReturn(Collections.singletonList(node2)); + RMContext rmContext = Mockito.mock(RMContext.class); + Mockito.when(rmContext.getScheduler()).thenReturn(scheduler); + RMNodeLabelsManager labMan = Mockito.mock(RMNodeLabelsManager.class); + Mockito.when(labMan.getNodesWithoutALabel()).thenReturn(noLabelNodes); + Mockito.when(labMan.getLabelsToNodes(Collections.singleton("label1"))) + .thenReturn(label1NodesMap); + Mockito.when(rmContext.getNodeLabelManager()).thenReturn(labMan); + + ResourceRequest anyReq = createResourceRequest(ResourceRequest.ANY, + true, null); + List reqs = new ArrayList<>(); + reqs.add(anyReq); + Assert.assertEquals(80, + RMServerUtils.getApplicableNodeCountForAM(rmContext, conf, reqs)); + + ResourceRequest rackReq = createResourceRequest("/rack1", true, null); + reqs.add(rackReq); + Assert.assertEquals(20, + RMServerUtils.getApplicableNodeCountForAM(rmContext, conf, reqs)); + anyReq.setRelaxLocality(false); + Assert.assertEquals(20, + RMServerUtils.getApplicableNodeCountForAM(rmContext, conf, reqs)); + rackReq.setRelaxLocality(false); + Assert.assertEquals(80, + RMServerUtils.getApplicableNodeCountForAM(rmContext, conf, reqs)); + + ResourceRequest node1Req = createResourceRequest("node1", false, null); + reqs.add(node1Req); + Assert.assertEquals(80, + RMServerUtils.getApplicableNodeCountForAM(rmContext, conf, reqs)); + node1Req.setRelaxLocality(true); + Assert.assertEquals(0, + RMServerUtils.getApplicableNodeCountForAM(rmContext, conf, reqs)); + rackReq.setRelaxLocality(true); + Assert.assertEquals(20, + RMServerUtils.getApplicableNodeCountForAM(rmContext, conf, reqs)); + + ResourceRequest node2Req = createResourceRequest("node2", false, null); + reqs.add(node2Req); + Assert.assertEquals(20, + RMServerUtils.getApplicableNodeCountForAM(rmContext, conf, reqs)); + node2Req.setRelaxLocality(true); + Assert.assertEquals(20, + RMServerUtils.getApplicableNodeCountForAM(rmContext, conf, reqs)); + rackReq.setRelaxLocality(false); + Assert.assertEquals(1, + RMServerUtils.getApplicableNodeCountForAM(rmContext, conf, reqs)); + node1Req.setRelaxLocality(false); + Assert.assertEquals(1, + RMServerUtils.getApplicableNodeCountForAM(rmContext, conf, reqs)); + node2Req.setRelaxLocality(false); + Assert.assertEquals(80, + RMServerUtils.getApplicableNodeCountForAM(rmContext, conf, reqs)); + + anyReq.setNodeLabelExpression("label1"); + rackReq.setNodeLabelExpression("label1"); + node1Req.setNodeLabelExpression("label1"); + node2Req.setNodeLabelExpression("label1"); + anyReq.setRelaxLocality(true); + reqs = new ArrayList<>(); + reqs.add(anyReq); + Assert.assertEquals(15, + RMServerUtils.getApplicableNodeCountForAM(rmContext, conf, reqs)); + + rackReq.setRelaxLocality(true); + reqs.add(rackReq); + Assert.assertEquals(10, + RMServerUtils.getApplicableNodeCountForAM(rmContext, conf, reqs)); + anyReq.setRelaxLocality(false); + Assert.assertEquals(10, + RMServerUtils.getApplicableNodeCountForAM(rmContext, conf, reqs)); + rackReq.setRelaxLocality(false); + Assert.assertEquals(15, + RMServerUtils.getApplicableNodeCountForAM(rmContext, conf, reqs)); + + node1Req.setRelaxLocality(false); + reqs.add(node1Req); + Assert.assertEquals(15, + RMServerUtils.getApplicableNodeCountForAM(rmContext, conf, reqs)); + node1Req.setRelaxLocality(true); + Assert.assertEquals(1, + RMServerUtils.getApplicableNodeCountForAM(rmContext, conf, reqs)); + rackReq.setRelaxLocality(true); + Assert.assertEquals(11, + RMServerUtils.getApplicableNodeCountForAM(rmContext, conf, reqs)); + + node2Req.setRelaxLocality(false); + reqs.add(node2Req); + Assert.assertEquals(11, + RMServerUtils.getApplicableNodeCountForAM(rmContext, conf, reqs)); + node2Req.setRelaxLocality(true); + Assert.assertEquals(11, + RMServerUtils.getApplicableNodeCountForAM(rmContext, conf, reqs)); + rackReq.setRelaxLocality(false); + Assert.assertEquals(1, + RMServerUtils.getApplicableNodeCountForAM(rmContext, conf, reqs)); + node1Req.setRelaxLocality(false); + Assert.assertEquals(0, + RMServerUtils.getApplicableNodeCountForAM(rmContext, conf, reqs)); + node2Req.setRelaxLocality(false); + Assert.assertEquals(15, + RMServerUtils.getApplicableNodeCountForAM(rmContext, conf, reqs)); + } + + private ResourceRequest createResourceRequest(String resource, + boolean relaxLocality, String nodeLabel) { + return ResourceRequest.newInstance(Priority.newInstance(0), + resource, Resource.newInstance(1, 1), 1, relaxLocality, nodeLabel); + } +} diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/applicationsmanager/MockAsm.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/applicationsmanager/MockAsm.java index 9be52c6d3b6..5246eb79a15 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/applicationsmanager/MockAsm.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/applicationsmanager/MockAsm.java @@ -57,7 +57,7 @@ import com.google.common.collect.Lists; public abstract class MockAsm extends MockApps { public static class ApplicationBase implements RMApp { - ResourceRequest amReq; + List amReqs; @Override public String getUser() { throw new UnsupportedOperationException("Not supported yet."); @@ -204,8 +204,8 @@ public abstract class MockAsm extends MockApps { } @Override - public ResourceRequest getAMResourceRequest() { - return this.amReq; + public List getAMResourceRequests() { + return this.amReqs; } @Override diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/metrics/TestSystemMetricsPublisher.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/metrics/TestSystemMetricsPublisher.java index 55e93c1e6cb..7005bca6585 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/metrics/TestSystemMetricsPublisher.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/metrics/TestSystemMetricsPublisher.java @@ -526,7 +526,8 @@ public class TestSystemMetricsPublisher { when(app.getAppNodeLabelExpression()).thenCallRealMethod(); ResourceRequest amReq = mock(ResourceRequest.class); when(amReq.getNodeLabelExpression()).thenReturn("high-mem"); - when(app.getAMResourceRequest()).thenReturn(amReq); + when(app.getAMResourceRequests()) + .thenReturn(Collections.singletonList(amReq)); when(app.getAmNodeLabelExpression()).thenCallRealMethod(); when(app.getApplicationPriority()).thenReturn(Priority.newInstance(10)); when(app.getCallerContext()) diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/rmapp/MockRMApp.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/rmapp/MockRMApp.java index 118b6bc7489..9290ff8faa0 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/rmapp/MockRMApp.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/rmapp/MockRMApp.java @@ -21,6 +21,7 @@ package org.apache.hadoop.yarn.server.resourcemanager.rmapp; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Set; @@ -62,14 +63,14 @@ public class MockRMApp implements RMApp { StringBuilder diagnostics = new StringBuilder(); RMAppAttempt attempt; int maxAppAttempts = 1; - ResourceRequest amReq; + List amReqs; public MockRMApp(int newid, long time, RMAppState newState) { finish = time; id = MockApps.newAppID(newid); state = newState; - amReq = ResourceRequest.newInstance(Priority.UNDEFINED, "0.0.0.0", - Resource.newInstance(0, 0), 1); + amReqs = Collections.singletonList(ResourceRequest.newInstance( + Priority.UNDEFINED, "0.0.0.0", Resource.newInstance(0, 0), 1)); } public MockRMApp(int newid, long time, RMAppState newState, String userName) { @@ -276,8 +277,8 @@ public class MockRMApp implements RMApp { } @Override - public ResourceRequest getAMResourceRequest() { - return this.amReq; + public List getAMResourceRequests() { + return this.amReqs; } @Override diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/rmapp/TestRMAppTransitions.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/rmapp/TestRMAppTransitions.java index 488485112cb..5aa7af9e519 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/rmapp/TestRMAppTransitions.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/rmapp/TestRMAppTransitions.java @@ -30,8 +30,10 @@ import static org.mockito.Mockito.verify; import java.io.IOException; import java.nio.ByteBuffer; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.Map; import org.apache.commons.logging.Log; @@ -271,7 +273,8 @@ public class TestRMAppTransitions { submissionContext.setAMContainerSpec(mock(ContainerLaunchContext.class)); RMApp application = new RMAppImpl(applicationId, rmContext, conf, name, user, queue, submissionContext, scheduler, masterService, - System.currentTimeMillis(), "YARN", null, mock(ResourceRequest.class)); + System.currentTimeMillis(), "YARN", null, + new ArrayList()); testAppStartState(applicationId, user, name, queue, application); this.rmContext.getRMApps().putIfAbsent(application.getApplicationId(), @@ -1024,9 +1027,9 @@ public class TestRMAppTransitions { submissionContext.getQueue(), submissionContext, scheduler, null, appState.getSubmitTime(), submissionContext.getApplicationType(), submissionContext.getApplicationTags(), - BuilderUtils.newResourceRequest( + Collections.singletonList(BuilderUtils.newResourceRequest( RMAppAttemptImpl.AM_CONTAINER_PRIORITY, ResourceRequest.ANY, - submissionContext.getResource(), 1)); + submissionContext.getResource(), 1))); Assert.assertEquals(RMAppState.NEW, application.getState()); RMAppEvent recoverEvent = diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/rmapp/attempt/TestRMAppAttemptTransitions.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/rmapp/attempt/TestRMAppAttemptTransitions.java index ced5bd9b8d8..9a4b6dc7f72 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/rmapp/attempt/TestRMAppAttemptTransitions.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/rmapp/attempt/TestRMAppAttemptTransitions.java @@ -328,9 +328,9 @@ public class TestRMAppAttemptTransitions { applicationAttempt = new RMAppAttemptImpl(applicationAttemptId, spyRMContext, scheduler, masterService, submissionContext, new Configuration(), - BuilderUtils.newResourceRequest( + Collections.singletonList(BuilderUtils.newResourceRequest( RMAppAttemptImpl.AM_CONTAINER_PRIORITY, ResourceRequest.ANY, - submissionContext.getResource(), 1), application); + submissionContext.getResource(), 1)), application); when(application.getCurrentAppAttempt()).thenReturn(applicationAttempt); when(application.getApplicationId()).thenReturn(applicationId); @@ -1108,9 +1108,9 @@ public class TestRMAppAttemptTransitions { new RMAppAttemptImpl(applicationAttempt.getAppAttemptId(), spyRMContext, scheduler,masterService, submissionContext, myConf, - BuilderUtils.newResourceRequest( + Collections.singletonList(BuilderUtils.newResourceRequest( RMAppAttemptImpl.AM_CONTAINER_PRIORITY, ResourceRequest.ANY, - submissionContext.getResource(), 1), application); + submissionContext.getResource(), 1)), application); //submit, schedule and allocate app attempt myApplicationAttempt.handle( @@ -1584,9 +1584,9 @@ public class TestRMAppAttemptTransitions { applicationAttempt = new RMAppAttemptImpl(applicationAttempt.getAppAttemptId(), spyRMContext, scheduler, masterService, submissionContext, new Configuration(), - BuilderUtils.newResourceRequest( + Collections.singletonList(BuilderUtils.newResourceRequest( RMAppAttemptImpl.AM_CONTAINER_PRIORITY, ResourceRequest.ANY, - submissionContext.getResource(), 1), application); + submissionContext.getResource(), 1)), application); when(submissionContext.getKeepContainersAcrossApplicationAttempts()) .thenReturn(true); when(submissionContext.getMaxAppAttempts()).thenReturn(1); @@ -1645,9 +1645,10 @@ public class TestRMAppAttemptTransitions { applicationAttempt = new RMAppAttemptImpl(applicationAttempt.getAppAttemptId(), spyRMContext, scheduler, masterService, submissionContext, - new Configuration(), ResourceRequest.newInstance( - Priority.UNDEFINED, "host1", Resource.newInstance(3333, 1), 3, - false, "label-expression"), application); + new Configuration(), Collections.singletonList( + ResourceRequest.newInstance(Priority.UNDEFINED, "host1", + Resource.newInstance(3333, 1), 3, + false, "label-expression")), application); new RMAppAttemptImpl.ScheduleTransition().transition( (RMAppAttemptImpl) applicationAttempt, null); } diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/TestApplicationLimits.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/TestApplicationLimits.java index bb0a1239fca..8aca235f8aa 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/TestApplicationLimits.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/TestApplicationLimits.java @@ -28,6 +28,7 @@ import static org.mockito.Mockito.when; import java.io.IOException; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -613,7 +614,8 @@ public class TestApplicationLimits { ResourceRequest amResourceRequest = mock(ResourceRequest.class); Resource amResource = Resources.createResource(0, 0); when(amResourceRequest.getCapability()).thenReturn(amResource); - when(rmApp.getAMResourceRequest()).thenReturn(amResourceRequest); + when(rmApp.getAMResourceRequests()).thenReturn( + Collections.singletonList(amResourceRequest)); Mockito.doReturn(rmApp).when(spyApps).get((ApplicationId)Matchers.any()); when(spyRMContext.getRMApps()).thenReturn(spyApps); RMAppAttempt rmAppAttempt = mock(RMAppAttempt.class); diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/TestApplicationLimitsByPartition.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/TestApplicationLimitsByPartition.java index b70a359f591..0aac2ef23da 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/TestApplicationLimitsByPartition.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/TestApplicationLimitsByPartition.java @@ -25,6 +25,7 @@ import static org.mockito.Mockito.when; import java.io.IOException; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -639,7 +640,8 @@ public class TestApplicationLimitsByPartition { ResourceRequest amResourceRequest = mock(ResourceRequest.class); Resource amResource = Resources.createResource(0, 0); when(amResourceRequest.getCapability()).thenReturn(amResource); - when(rmApp.getAMResourceRequest()).thenReturn(amResourceRequest); + when(rmApp.getAMResourceRequests()).thenReturn( + Collections.singletonList(amResourceRequest)); Mockito.doReturn(rmApp).when(spyApps).get((ApplicationId) Matchers.any()); when(spyRMContext.getRMApps()).thenReturn(spyApps); RMAppAttempt rmAppAttempt = mock(RMAppAttempt.class); diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/TestCapacityScheduler.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/TestCapacityScheduler.java index 293bac21174..ff0f7cfc377 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/TestCapacityScheduler.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/TestCapacityScheduler.java @@ -3205,7 +3205,7 @@ public class TestCapacityScheduler { RMApp rmApp = rm.submitApp(amMemory, "app-1", "user_0", null, queueName); assertEquals("RMApp does not containes minimum allocation", - minAllocResource, rmApp.getAMResourceRequest().getCapability()); + minAllocResource, rmApp.getAMResourceRequests().get(0).getCapability()); ResourceScheduler scheduler = rm.getRMContext().getScheduler(); LeafQueue queueA = diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/TestLeafQueue.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/TestLeafQueue.java index 3fbbae3f59a..1162b9fc7b4 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/TestLeafQueue.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/TestLeafQueue.java @@ -151,7 +151,8 @@ public class TestLeafQueue { amResourceRequest = mock(ResourceRequest.class); when(amResourceRequest.getCapability()).thenReturn( Resources.createResource(0, 0)); - when(rmApp.getAMResourceRequest()).thenReturn(amResourceRequest); + when(rmApp.getAMResourceRequests()).thenReturn( + Collections.singletonList(amResourceRequest)); Mockito.doReturn(rmApp).when(spyApps).get((ApplicationId)Matchers.any()); when(spyRMContext.getRMApps()).thenReturn(spyApps); diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/fair/TestFairScheduler.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/fair/TestFairScheduler.java index baf74346fed..9bf79f6100d 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/fair/TestFairScheduler.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/fair/TestFairScheduler.java @@ -3204,6 +3204,84 @@ public class TestFairScheduler extends FairSchedulerTestBase { assertEquals(1, app.getLiveContainers().size()); } + @Test + public void testAMStrictLocalityRack() throws IOException { + testAMStrictLocality(false, false); + } + + @Test + public void testAMStrictLocalityNode() throws IOException { + testAMStrictLocality(true, false); + } + + @Test + public void testAMStrictLocalityRackInvalid() throws IOException { + testAMStrictLocality(false, true); + } + + @Test + public void testAMStrictLocalityNodeInvalid() throws IOException { + testAMStrictLocality(true, true); + } + + private void testAMStrictLocality(boolean node, boolean invalid) + throws IOException { + scheduler.init(conf); + scheduler.start(); + scheduler.reinitialize(conf, resourceManager.getRMContext()); + + RMNode node1 = MockNodes.newNodeInfo(1, Resources.createResource(1024), 1, + "127.0.0.1"); + NodeAddedSchedulerEvent nodeEvent1 = new NodeAddedSchedulerEvent(node1); + scheduler.handle(nodeEvent1); + + RMNode node2 = MockNodes.newNodeInfo(2, Resources.createResource(1024), 2, + "127.0.0.2"); + NodeAddedSchedulerEvent nodeEvent2 = new NodeAddedSchedulerEvent(node2); + scheduler.handle(nodeEvent2); + + List reqs = new ArrayList<>(); + ResourceRequest nodeRequest = createResourceRequest(1024, + node2.getHostName(), 1, 1, true); + if (node && invalid) { + nodeRequest.setResourceName("invalid"); + } + ResourceRequest rackRequest = createResourceRequest(1024, + node2.getRackName(), 1, 1, !node); + if (!node && invalid) { + rackRequest.setResourceName("invalid"); + } + ResourceRequest anyRequest = createResourceRequest(1024, + ResourceRequest.ANY, 1, 1, false); + reqs.add(anyRequest); + reqs.add(rackRequest); + if (node) { + reqs.add(nodeRequest); + } + + ApplicationAttemptId attId1 = + createSchedulingRequest("queue1", "user1", reqs); + + scheduler.update(); + + NodeUpdateSchedulerEvent node2UpdateEvent = + new NodeUpdateSchedulerEvent(node2); + + FSAppAttempt app = scheduler.getSchedulerApp(attId1); + + // node2 should get the container + scheduler.handle(node2UpdateEvent); + if (invalid) { + assertEquals(0, app.getLiveContainers().size()); + assertEquals(0, scheduler.getNode(node2.getNodeID()).getNumContainers()); + assertEquals(0, scheduler.getNode(node1.getNodeID()).getNumContainers()); + } else { + assertEquals(1, app.getLiveContainers().size()); + assertEquals(1, scheduler.getNode(node2.getNodeID()).getNumContainers()); + assertEquals(0, scheduler.getNode(node1.getNodeID()).getNumContainers()); + } + } + /** * Strict locality requests shouldn't reserve resources on another node. */ diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/TestRMWebServicesApps.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/TestRMWebServicesApps.java index 30f25e950e3..fb9e8edfb0e 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/TestRMWebServicesApps.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/TestRMWebServicesApps.java @@ -1213,8 +1213,8 @@ public class TestRMWebServicesApps extends JerseyTestBase { assertEquals(app1.getApplicationId().toString(), appInfo.getAppId()); assertEquals(app1.getName(), appInfo.getName()); assertEquals(app1.createApplicationState(), appInfo.getState()); - assertEquals(app1.getAMResourceRequest().getCapability().getMemorySize(), - appInfo.getAllocatedMB()); + assertEquals(app1.getAMResourceRequests().get(0).getCapability() + .getMemorySize(), appInfo.getAllocatedMB()); rm.stop(); } @@ -1427,7 +1427,7 @@ public class TestRMWebServicesApps extends JerseyTestBase { expectedNumberOfElements++; appNodeLabelExpression = info.getString("appNodeLabelExpression"); } - if (app.getAMResourceRequest().getNodeLabelExpression() != null) { + if (app.getAMResourceRequests().get(0).getNodeLabelExpression() != null) { expectedNumberOfElements++; amNodeLabelExpression = info.getString("amNodeLabelExpression"); } @@ -1534,7 +1534,7 @@ public class TestRMWebServicesApps extends JerseyTestBase { app.getApplicationSubmissionContext().getNodeLabelExpression(), appNodeLabelExpression); assertEquals("unmanagedApplication doesn't match", - app.getAMResourceRequest().getNodeLabelExpression(), + app.getAMResourceRequests().get(0).getNodeLabelExpression(), amNodeLabelExpression); assertEquals("amRPCAddress", AppInfo.getAmRPCAddressFromRMAppAttempt(app.getCurrentAppAttempt()), @@ -1561,7 +1561,7 @@ public class TestRMWebServicesApps extends JerseyTestBase { String nodeLabelExpression, int numContainers, boolean relaxLocality, int priority, String resourceName, long memory, long vCores, String executionType, boolean enforceExecutionType) { - ResourceRequest request = app.getAMResourceRequest(); + ResourceRequest request = app.getAMResourceRequests().get(0); assertEquals("nodeLabelExpression doesn't match", request.getNodeLabelExpression(), nodeLabelExpression); assertEquals("numContainers doesn't match", request.getNumContainers(), From 448ec81fd7133f413853570f116d5f6e16f68bd9 Mon Sep 17 00:00:00 2001 From: Akira Ajisaka Date: Tue, 28 Mar 2017 14:32:40 +0900 Subject: [PATCH 145/188] HADOOP-14218. Replace assertThat with assertTrue in MetricsAsserts. --- .../test/java/org/apache/hadoop/test/MetricsAsserts.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/test/MetricsAsserts.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/test/MetricsAsserts.java index a7bbe841366..b2f6054fe85 100644 --- a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/test/MetricsAsserts.java +++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/test/MetricsAsserts.java @@ -27,7 +27,6 @@ import static org.mockito.AdditionalMatchers.geq; import static org.mockito.Mockito.*; import org.mockito.stubbing.Answer; -import org.mockito.internal.matchers.GreaterThan; import org.mockito.invocation.InvocationOnMock; import org.mockito.ArgumentCaptor; @@ -329,8 +328,8 @@ public class MetricsAsserts { */ public static void assertCounterGt(String name, long greater, MetricsRecordBuilder rb) { - Assert.assertThat("Bad value for metric " + name, getLongCounter(name, rb), - new GreaterThan(greater)); + Assert.assertTrue("Bad value for metric " + name, + getLongCounter(name, rb) > greater); } /** @@ -352,8 +351,8 @@ public class MetricsAsserts { */ public static void assertGaugeGt(String name, double greater, MetricsRecordBuilder rb) { - Assert.assertThat("Bad value for metric " + name, getDoubleGauge(name, rb), - new GreaterThan(greater)); + Assert.assertTrue("Bad value for metric " + name, + getDoubleGauge(name, rb) > greater); } /** From 253e3e78abb9ebe0e8103d25538ff07875b33c84 Mon Sep 17 00:00:00 2001 From: John Zhuge Date: Mon, 20 Feb 2017 21:27:46 -0800 Subject: [PATCH 146/188] HADOOP-14038. Rename ADLS credential properties. Contributed by John Zhuge. --- .../org/apache/hadoop/conf/Configuration.java | 15 +++- .../org/apache/hadoop/fs/adl/AdlConfKeys.java | 38 +++++++-- .../apache/hadoop/fs/adl/AdlFileSystem.java | 4 + .../src/site/markdown/index.md | 22 ++--- .../fs/adl/TestAzureADTokenProvider.java | 5 +- .../fs/adl/TestValidateConfiguration.java | 83 +++++++++++++++++-- 6 files changed, 138 insertions(+), 29 deletions(-) diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/conf/Configuration.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/conf/Configuration.java index ada8b0202e6..da4f6ac6b3f 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/conf/Configuration.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/conf/Configuration.java @@ -751,7 +751,20 @@ public class Configuration implements Iterable>, this.loadDefaults = other.loadDefaults; setQuietMode(other.getQuietMode()); } - + + /** + * Reload existing configuration instances. + */ + public static synchronized void reloadExistingConfigurations() { + if (LOG.isDebugEnabled()) { + LOG.debug("Reloading " + REGISTRY.keySet().size() + + " existing configurations"); + } + for (Configuration conf : REGISTRY.keySet()) { + conf.reloadConfiguration(); + } + } + /** * Add a default resource. Resources are loaded in the order of the resources * added. diff --git a/hadoop-tools/hadoop-azure-datalake/src/main/java/org/apache/hadoop/fs/adl/AdlConfKeys.java b/hadoop-tools/hadoop-azure-datalake/src/main/java/org/apache/hadoop/fs/adl/AdlConfKeys.java index 8fc8e00b84c..d3a5565e6d5 100644 --- a/hadoop-tools/hadoop-azure-datalake/src/main/java/org/apache/hadoop/fs/adl/AdlConfKeys.java +++ b/hadoop-tools/hadoop-azure-datalake/src/main/java/org/apache/hadoop/fs/adl/AdlConfKeys.java @@ -20,6 +20,8 @@ package org.apache.hadoop.fs.adl; import org.apache.hadoop.classification.InterfaceAudience; import org.apache.hadoop.classification.InterfaceStability; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.conf.Configuration.DeprecationDelta; /** * Constants. @@ -28,25 +30,25 @@ import org.apache.hadoop.classification.InterfaceStability; @InterfaceStability.Evolving public final class AdlConfKeys { // OAuth2 Common Configuration - public static final String AZURE_AD_REFRESH_URL_KEY = "dfs.adls.oauth2" - + ".refresh.url"; + public static final String AZURE_AD_REFRESH_URL_KEY = + "fs.adl.oauth2.refresh.url"; // optional when provider type is refresh or client id. public static final String AZURE_AD_TOKEN_PROVIDER_CLASS_KEY = - "dfs.adls.oauth2.access.token.provider"; + "fs.adl.oauth2.access.token.provider"; public static final String AZURE_AD_CLIENT_ID_KEY = - "dfs.adls.oauth2.client.id"; + "fs.adl.oauth2.client.id"; public static final String AZURE_AD_TOKEN_PROVIDER_TYPE_KEY = - "dfs.adls.oauth2.access.token.provider.type"; + "fs.adl.oauth2.access.token.provider.type"; // OAuth Refresh Token Configuration public static final String AZURE_AD_REFRESH_TOKEN_KEY = - "dfs.adls.oauth2.refresh.token"; + "fs.adl.oauth2.refresh.token"; public static final String TOKEN_PROVIDER_TYPE_REFRESH_TOKEN = "RefreshToken"; // OAuth Client Cred Token Configuration public static final String AZURE_AD_CLIENT_SECRET_KEY = - "dfs.adls.oauth2.credential"; + "fs.adl.oauth2.credential"; public static final String TOKEN_PROVIDER_TYPE_CLIENT_CRED = "ClientCredential"; @@ -75,7 +77,7 @@ public final class AdlConfKeys { static final int DEFAULT_WRITE_AHEAD_BUFFER_SIZE = 4 * 1024 * 1024; static final String LATENCY_TRACKER_KEY = - "adl.dfs.enable.client.latency.tracker"; + "adl.enable.client.latency.tracker"; static final boolean LATENCY_TRACKER_DEFAULT = true; static final String ADL_EXPERIMENT_POSITIONAL_READ_KEY = @@ -90,6 +92,26 @@ public final class AdlConfKeys { "adl.feature.ownerandgroup.enableupn"; static final boolean ADL_ENABLEUPN_FOR_OWNERGROUP_DEFAULT = false; + public static void addDeprecatedKeys() { + Configuration.addDeprecations(new DeprecationDelta[]{ + new DeprecationDelta("dfs.adls.oauth2.access.token.provider.type", + AZURE_AD_TOKEN_PROVIDER_TYPE_KEY), + new DeprecationDelta("dfs.adls.oauth2.client.id", + AZURE_AD_CLIENT_ID_KEY), + new DeprecationDelta("dfs.adls.oauth2.refresh.token", + AZURE_AD_REFRESH_TOKEN_KEY), + new DeprecationDelta("dfs.adls.oauth2.refresh.url", + AZURE_AD_REFRESH_URL_KEY), + new DeprecationDelta("dfs.adls.oauth2.credential", + AZURE_AD_CLIENT_SECRET_KEY), + new DeprecationDelta("dfs.adls.oauth2.access.token.provider", + AZURE_AD_TOKEN_PROVIDER_CLASS_KEY), + new DeprecationDelta("adl.dfs.enable.client.latency.tracker", + LATENCY_TRACKER_KEY) + }); + Configuration.reloadExistingConfigurations(); + } + private AdlConfKeys() { } } diff --git a/hadoop-tools/hadoop-azure-datalake/src/main/java/org/apache/hadoop/fs/adl/AdlFileSystem.java b/hadoop-tools/hadoop-azure-datalake/src/main/java/org/apache/hadoop/fs/adl/AdlFileSystem.java index e0e273e6f21..0b860b36592 100644 --- a/hadoop-tools/hadoop-azure-datalake/src/main/java/org/apache/hadoop/fs/adl/AdlFileSystem.java +++ b/hadoop-tools/hadoop-azure-datalake/src/main/java/org/apache/hadoop/fs/adl/AdlFileSystem.java @@ -88,6 +88,10 @@ public class AdlFileSystem extends FileSystem { private AccessTokenProvider tokenProvider; private AzureADTokenProvider azureTokenProvider; + static { + AdlConfKeys.addDeprecatedKeys(); + } + @Override public String getScheme() { return SCHEME; diff --git a/hadoop-tools/hadoop-azure-datalake/src/site/markdown/index.md b/hadoop-tools/hadoop-azure-datalake/src/site/markdown/index.md index de8fda25d17..d2da858dfc7 100644 --- a/hadoop-tools/hadoop-azure-datalake/src/site/markdown/index.md +++ b/hadoop-tools/hadoop-azure-datalake/src/site/markdown/index.md @@ -83,7 +83,7 @@ Add the following properties to the cluster's `core-site.xml` ```xml - dfs.adls.oauth2.access.token.provider.type + fs.adl.oauth2.access.token.provider.type RefreshToken ``` @@ -95,12 +95,12 @@ service associated with the client id. See [*Active Directory Library For Java*] ```xml - dfs.adls.oauth2.client.id + fs.adl.oauth2.client.id - dfs.adls.oauth2.refresh.token + fs.adl.oauth2.refresh.token ``` @@ -133,22 +133,22 @@ Add the following properties to your `core-site.xml` ```xml - dfs.adls.oauth2.access.token.provider.type + fs.adl.oauth2.access.token.provider.type ClientCredential - dfs.adls.oauth2.refresh.url + fs.adl.oauth2.refresh.url TOKEN ENDPOINT FROM STEP 7 ABOVE - dfs.adls.oauth2.client.id + fs.adl.oauth2.client.id CLIENT ID FROM STEP 7 ABOVE - dfs.adls.oauth2.credential + fs.adl.oauth2.credential PASSWORD FROM STEP 7 ABOVE ``` @@ -166,9 +166,9 @@ For additional reading on the credential provider API, see ##### Provisioning ```bash -hadoop credential create dfs.adls.oauth2.client.id -value 123 +hadoop credential create fs.adl.oauth2.client.id -value 123 -provider localjceks://file/home/foo/adls.jceks -hadoop credential create dfs.adls.oauth2.refresh.token -value 123 +hadoop credential create fs.adl.oauth2.refresh.token -value 123 -provider localjceks://file/home/foo/adls.jceks ``` @@ -176,7 +176,7 @@ hadoop credential create dfs.adls.oauth2.refresh.token -value 123 ```xml - dfs.adls.oauth2.access.token.provider.type + fs.adl.oauth2.access.token.provider.type RefreshToken @@ -190,7 +190,7 @@ hadoop credential create dfs.adls.oauth2.refresh.token -value 123 ```bash hadoop distcp - [-D dfs.adls.oauth2.access.token.provider.type=RefreshToken + [-D fs.adl.oauth2.access.token.provider.type=RefreshToken -D hadoop.security.credential.provider.path=localjceks://file/home/user/adls.jceks] hdfs://:9001/user/foo/srcDir adl://.azuredatalakestore.net/tgtDir/ diff --git a/hadoop-tools/hadoop-azure-datalake/src/test/java/org/apache/hadoop/fs/adl/TestAzureADTokenProvider.java b/hadoop-tools/hadoop-azure-datalake/src/test/java/org/apache/hadoop/fs/adl/TestAzureADTokenProvider.java index 70f2a7f992f..3867e74fa77 100644 --- a/hadoop-tools/hadoop-azure-datalake/src/test/java/org/apache/hadoop/fs/adl/TestAzureADTokenProvider.java +++ b/hadoop-tools/hadoop-azure-datalake/src/test/java/org/apache/hadoop/fs/adl/TestAzureADTokenProvider.java @@ -46,6 +46,7 @@ import static org.junit.Assert.assertEquals; import org.apache.hadoop.security.ProviderUtils; import org.apache.hadoop.security.alias.CredentialProvider; import org.apache.hadoop.security.alias.CredentialProviderFactory; +import org.apache.hadoop.test.GenericTestUtils; import org.junit.Assert; import org.junit.Rule; import org.junit.Test; @@ -121,8 +122,8 @@ public class TestAzureADTokenProvider { Assert.fail("Initialization should have failed due no token provider " + "configuration"); } catch (IllegalArgumentException e) { - Assert.assertTrue( - e.getMessage().contains("dfs.adls.oauth2.access.token.provider")); + GenericTestUtils.assertExceptionContains( + AZURE_AD_TOKEN_PROVIDER_CLASS_KEY, e); } conf.setClass(AZURE_AD_TOKEN_PROVIDER_CLASS_KEY, CustomMockTokenProvider.class, AzureADTokenProvider.class); diff --git a/hadoop-tools/hadoop-azure-datalake/src/test/java/org/apache/hadoop/fs/adl/TestValidateConfiguration.java b/hadoop-tools/hadoop-azure-datalake/src/test/java/org/apache/hadoop/fs/adl/TestValidateConfiguration.java index 4cabaa3d08d..3d51b42111e 100644 --- a/hadoop-tools/hadoop-azure-datalake/src/test/java/org/apache/hadoop/fs/adl/TestValidateConfiguration.java +++ b/hadoop-tools/hadoop-azure-datalake/src/test/java/org/apache/hadoop/fs/adl/TestValidateConfiguration.java @@ -18,6 +18,8 @@ package org.apache.hadoop.fs.adl; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.test.GenericTestUtils; import org.junit.Assert; import org.junit.Test; @@ -56,6 +58,11 @@ import static org.apache.hadoop.fs.adl.AdlConfKeys .TOKEN_PROVIDER_TYPE_REFRESH_TOKEN; import static org.apache.hadoop.fs.adl.AdlConfKeys.WRITE_BUFFER_SIZE_KEY; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; + /** * Validate configuration keys defined for adl storage file system instance. */ @@ -64,18 +71,18 @@ public class TestValidateConfiguration { @Test public void validateConfigurationKeys() { Assert - .assertEquals("dfs.adls.oauth2.refresh.url", AZURE_AD_REFRESH_URL_KEY); - Assert.assertEquals("dfs.adls.oauth2.access.token.provider", + .assertEquals("fs.adl.oauth2.refresh.url", AZURE_AD_REFRESH_URL_KEY); + Assert.assertEquals("fs.adl.oauth2.access.token.provider", AZURE_AD_TOKEN_PROVIDER_CLASS_KEY); - Assert.assertEquals("dfs.adls.oauth2.client.id", AZURE_AD_CLIENT_ID_KEY); - Assert.assertEquals("dfs.adls.oauth2.refresh.token", + Assert.assertEquals("fs.adl.oauth2.client.id", AZURE_AD_CLIENT_ID_KEY); + Assert.assertEquals("fs.adl.oauth2.refresh.token", AZURE_AD_REFRESH_TOKEN_KEY); Assert - .assertEquals("dfs.adls.oauth2.credential", AZURE_AD_CLIENT_SECRET_KEY); + .assertEquals("fs.adl.oauth2.credential", AZURE_AD_CLIENT_SECRET_KEY); Assert.assertEquals("adl.debug.override.localuserasfileowner", ADL_DEBUG_OVERRIDE_LOCAL_USER_AS_OWNER); - Assert.assertEquals("dfs.adls.oauth2.access.token.provider.type", + Assert.assertEquals("fs.adl.oauth2.access.token.provider.type", AZURE_AD_TOKEN_PROVIDER_TYPE_KEY); Assert.assertEquals("adl.feature.client.cache.readahead", @@ -88,7 +95,7 @@ public class TestValidateConfiguration { Assert.assertEquals("ClientCredential", TOKEN_PROVIDER_TYPE_CLIENT_CRED); - Assert.assertEquals("adl.dfs.enable.client.latency.tracker", + Assert.assertEquals("adl.enable.client.latency.tracker", LATENCY_TRACKER_KEY); Assert.assertEquals(true, LATENCY_TRACKER_DEFAULT); @@ -109,4 +116,66 @@ public class TestValidateConfiguration { Assert.assertEquals(false, ADL_ENABLEUPN_FOR_OWNERGROUP_DEFAULT); } + + @Test + public void testSetDeprecatedKeys() throws ClassNotFoundException { + Configuration conf = new Configuration(true); + setDeprecatedKeys(conf); + + // Force AdlFileSystem static initialization to register deprecated keys. + Class.forName(AdlFileSystem.class.getName()); + + assertDeprecatedKeys(conf); + } + + @Test + public void testLoadDeprecatedKeys() + throws IOException, ClassNotFoundException { + Configuration saveConf = new Configuration(false); + setDeprecatedKeys(saveConf); + + final File testRootDir = GenericTestUtils.getTestDir(); + File confXml = new File(testRootDir, "testLoadDeprecatedKeys.xml"); + OutputStream out = new FileOutputStream(confXml); + saveConf.writeXml(out); + out.close(); + + Configuration conf = new Configuration(true); + conf.addResource(confXml.toURI().toURL()); + + // Trigger loading the configuration resources by getting any key. + conf.get("dummy.key"); + + // Force AdlFileSystem static initialization to register deprecated keys. + Class.forName(AdlFileSystem.class.getName()); + + assertDeprecatedKeys(conf); + } + + private void setDeprecatedKeys(Configuration conf) { + conf.set("dfs.adls.oauth2.access.token.provider.type", "dummyType"); + conf.set("dfs.adls.oauth2.client.id", "dummyClientId"); + conf.set("dfs.adls.oauth2.refresh.token", "dummyRefreshToken"); + conf.set("dfs.adls.oauth2.refresh.url", "dummyRefreshUrl"); + conf.set("dfs.adls.oauth2.credential", "dummyCredential"); + conf.set("dfs.adls.oauth2.access.token.provider", "dummyClass"); + conf.set("adl.dfs.enable.client.latency.tracker", "dummyTracker"); + } + + private void assertDeprecatedKeys(Configuration conf) { + Assert.assertEquals("dummyType", + conf.get(AZURE_AD_TOKEN_PROVIDER_TYPE_KEY)); + Assert.assertEquals("dummyClientId", + conf.get(AZURE_AD_CLIENT_ID_KEY)); + Assert.assertEquals("dummyRefreshToken", + conf.get(AZURE_AD_REFRESH_TOKEN_KEY)); + Assert.assertEquals("dummyRefreshUrl", + conf.get(AZURE_AD_REFRESH_URL_KEY)); + Assert.assertEquals("dummyCredential", + conf.get(AZURE_AD_CLIENT_SECRET_KEY)); + Assert.assertEquals("dummyClass", + conf.get(AZURE_AD_TOKEN_PROVIDER_CLASS_KEY)); + Assert.assertEquals("dummyTracker", + conf.get(LATENCY_TRACKER_KEY)); + } } From fdf8f8ebca9987a1956ce464fe33ea6a3ad28d72 Mon Sep 17 00:00:00 2001 From: Jason Lowe Date: Tue, 28 Mar 2017 09:33:26 -0500 Subject: [PATCH 147/188] YARN-6359. TestRM#testApplicationKillAtAcceptedState fails rarely due to race condition. Contributed by Robert Kanter --- .../hadoop/yarn/server/resourcemanager/TestRM.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestRM.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestRM.java index cdf582e1f21..39313d06bd4 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestRM.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestRM.java @@ -18,6 +18,8 @@ package org.apache.hadoop.yarn.server.resourcemanager; +import com.google.common.base.Supplier; +import org.apache.hadoop.test.GenericTestUtils; import org.apache.hadoop.yarn.event.DrainDispatcher; import org.junit.Before; import static org.mockito.Matchers.argThat; @@ -631,7 +633,13 @@ public class TestRM extends ParameterizedSchedulerTestBase { rm.waitForState(application.getApplicationId(), RMAppState.KILLED); // test metrics - metrics = rm.getResourceScheduler().getRootQueueMetrics(); + GenericTestUtils.waitFor(new Supplier() { + @Override + public Boolean get() { + return appsKilled + 1 == metrics.getAppsKilled() + && appsSubmitted + 1 == metrics.getAppsSubmitted(); + } + }, 100, 10000); Assert.assertEquals(appsKilled + 1, metrics.getAppsKilled()); Assert.assertEquals(appsSubmitted + 1, metrics.getAppsSubmitted()); } From 6b0933643835d7696ced011cfdb8b74f63022e8b Mon Sep 17 00:00:00 2001 From: Yiqun Lin Date: Tue, 28 Mar 2017 23:02:07 +0800 Subject: [PATCH 148/188] HDFS-11577. Combine the old and the new chooseRandom for better performance. Contributed by Chen Liang. --- .../apache/hadoop/net/NetworkTopology.java | 2 +- .../hadoop/hdfs/net/DFSNetworkTopology.java | 76 +++++++++++++++++-- .../hdfs/net/TestDFSNetworkTopology.java | 57 ++++++++++++++ 3 files changed, 126 insertions(+), 9 deletions(-) diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/net/NetworkTopology.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/net/NetworkTopology.java index 051012b79df..f8cecddaeb8 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/net/NetworkTopology.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/net/NetworkTopology.java @@ -496,7 +496,7 @@ public class NetworkTopology { } } - private Node chooseRandom(final String scope, String excludedScope, + protected Node chooseRandom(final String scope, String excludedScope, final Collection excludedNodes) { if (excludedScope != null) { if (scope.startsWith(excludedScope)) { diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/net/DFSNetworkTopology.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/net/DFSNetworkTopology.java index ee83dba0095..259e2759dbd 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/net/DFSNetworkTopology.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/net/DFSNetworkTopology.java @@ -36,7 +36,6 @@ import java.util.Random; * remaining parts should be the same. * * Currently a placeholder to test storage type info. - * TODO : add "chooseRandom with storageType info" function. */ public class DFSNetworkTopology extends NetworkTopology { @@ -56,6 +55,7 @@ public class DFSNetworkTopology extends NetworkTopology { * * @param scope range of nodes from which a node will be chosen * @param excludedNodes nodes to be excluded from + * @param type the storage type we search for * @return the chosen node */ public Node chooseRandomWithStorageType(final String scope, @@ -74,6 +74,69 @@ public class DFSNetworkTopology extends NetworkTopology { } } + /** + * Randomly choose one node from scope with the given storage type. + * + * If scope starts with ~, choose one from the all nodes except for the + * ones in scope; otherwise, choose one from scope. + * If excludedNodes is given, choose a node that's not in excludedNodes. + * + * This call would make up to two calls. It first tries to get a random node + * (with old method) and check if it satisfies. If yes, simply return it. + * Otherwise, it make a second call (with the new method) by passing in a + * storage type. + * + * This is for better performance reason. Put in short, the key note is that + * the old method is faster but may take several runs, while the new method + * is somewhat slower, and always succeed in one trial. + * See HDFS-11535 for more detail. + * + * @param scope range of nodes from which a node will be chosen + * @param excludedNodes nodes to be excluded from + * @param type the storage type we search for + * @return the chosen node + */ + public Node chooseRandomWithStorageTypeTwoTrial(final String scope, + final Collection excludedNodes, StorageType type) { + netlock.readLock().lock(); + try { + String searchScope; + String excludedScope; + if (scope.startsWith("~")) { + searchScope = NodeBase.ROOT; + excludedScope = scope.substring(1); + } else { + searchScope = scope; + excludedScope = null; + } + // next do a two-trial search + // first trial, call the old method, inherited from NetworkTopology + Node n = chooseRandom(searchScope, excludedScope, excludedNodes); + if (n == null) { + if (LOG.isDebugEnabled()) { + LOG.debug("No node to choose."); + } + // this means there is simply no node to choose from + return null; + } + Preconditions.checkArgument(n instanceof DatanodeDescriptor); + DatanodeDescriptor dnDescriptor = (DatanodeDescriptor)n; + + if (dnDescriptor.hasStorageType(type)) { + // the first trial succeeded, just return + return dnDescriptor; + } else { + // otherwise, make the second trial by calling the new method + LOG.debug("First trial failed, node has no type {}, " + + "making second trial carrying this type", type); + return chooseRandomWithStorageType(searchScope, excludedScope, + excludedNodes, type); + } + } finally { + netlock.readLock().unlock(); + } + } + /** * Choose a random node based on given scope, excludedScope and excludedNodes * set. Although in general the topology has at most three layers, this class @@ -99,13 +162,10 @@ public class DFSNetworkTopology extends NetworkTopology { * all it's ancestors' storage counters accordingly, this way the excluded * root is out of the picture. * - * TODO : this function has duplicate code as NetworkTopology, need to - * refactor in the future. - * - * @param scope - * @param excludedScope - * @param excludedNodes - * @return + * @param scope the scope where we look for node. + * @param excludedScope the scope where the node must NOT be from. + * @param excludedNodes the returned node must not be in this set + * @return a node with required storage type */ @VisibleForTesting Node chooseRandomWithStorageType(final String scope, diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/net/TestDFSNetworkTopology.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/net/TestDFSNetworkTopology.java index 30ef2ac7f27..26d96b2309e 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/net/TestDFSNetworkTopology.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/net/TestDFSNetworkTopology.java @@ -508,4 +508,61 @@ public class TestDFSNetworkTopology { assertEquals(1, innerl2d3r3.getSubtreeStorageCount(StorageType.DISK)); } + + @Test + public void testChooseRandomWithStorageTypeTwoTrial() throws Exception { + Node n; + DatanodeDescriptor dd; + n = CLUSTER.chooseRandomWithStorageType("/l2/d3/r4", null, null, + StorageType.ARCHIVE); + HashSet excluded = new HashSet<>(); + // exclude the host on r4 (since there is only one host, no randomness here) + excluded.add(n); + + // search with given scope being desired scope + for (int i = 0; i<10; i++) { + n = CLUSTER.chooseRandomWithStorageTypeTwoTrial( + "/l2/d3", null, StorageType.ARCHIVE); + assertTrue(n instanceof DatanodeDescriptor); + dd = (DatanodeDescriptor) n; + assertTrue(dd.getHostName().equals("host12") || + dd.getHostName().equals("host13")); + } + + for (int i = 0; i<10; i++) { + n = CLUSTER.chooseRandomWithStorageTypeTwoTrial( + "/l2/d3", excluded, StorageType.ARCHIVE); + assertTrue(n instanceof DatanodeDescriptor); + dd = (DatanodeDescriptor) n; + assertTrue(dd.getHostName().equals("host13")); + } + + // search with given scope being exclude scope + + // a total of 4 ramdisk nodes: + // /l1/d2/r3/host7, /l2/d3/r2/host10, /l2/d4/r1/host7 and /l2/d4/r1/host10 + // so if we exclude /l2/d4/r1, if should be always either host7 or host10 + for (int i = 0; i<10; i++) { + n = CLUSTER.chooseRandomWithStorageTypeTwoTrial( + "~/l2/d4", null, StorageType.RAM_DISK); + assertTrue(n instanceof DatanodeDescriptor); + dd = (DatanodeDescriptor) n; + assertTrue(dd.getHostName().equals("host7") || + dd.getHostName().equals("host10")); + } + + // similar to above, except that we also exclude host10 here. so it should + // always be host7 + n = CLUSTER.chooseRandomWithStorageType("/l2/d3/r2", null, null, + StorageType.RAM_DISK); + // add host10 to exclude + excluded.add(n); + for (int i = 0; i<10; i++) { + n = CLUSTER.chooseRandomWithStorageTypeTwoTrial( + "~/l2/d4", excluded, StorageType.RAM_DISK); + assertTrue(n instanceof DatanodeDescriptor); + dd = (DatanodeDescriptor) n; + assertTrue(dd.getHostName().equals("host7")); + } + } } From 01aca54a22c8586d232a8f79fe9977aeb8d09b83 Mon Sep 17 00:00:00 2001 From: Varun Saxena Date: Wed, 29 Mar 2017 01:53:20 +0530 Subject: [PATCH 149/188] YARN-5368. Memory leak in timeline server (Jonathan Eagles via Varun Saxena) --- .../timeline/RollingLevelDBTimelineStore.java | 263 ++++++++---------- 1 file changed, 117 insertions(+), 146 deletions(-) diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/timeline/RollingLevelDBTimelineStore.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/timeline/RollingLevelDBTimelineStore.java index 4d38008c993..20e0379ac62 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/timeline/RollingLevelDBTimelineStore.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/timeline/RollingLevelDBTimelineStore.java @@ -275,9 +275,7 @@ public class RollingLevelDBTimelineStore extends AbstractService implements Path domainDBPath = new Path(dbPath, DOMAIN); Path starttimeDBPath = new Path(dbPath, STARTTIME); Path ownerDBPath = new Path(dbPath, OWNER); - FileSystem localFS = null; - try { - localFS = FileSystem.getLocal(conf); + try (FileSystem localFS = FileSystem.getLocal(conf)) { if (!localFS.exists(dbPath)) { if (!localFS.mkdirs(dbPath)) { throw new IOException("Couldn't create directory for leveldb " @@ -306,8 +304,6 @@ public class RollingLevelDBTimelineStore extends AbstractService implements } localFS.setPermission(ownerDBPath, LEVELDB_DIR_UMASK); } - } finally { - IOUtils.cleanup(LOG, localFS); } options.maxOpenFiles(conf.getInt( TIMELINE_SERVICE_LEVELDB_MAX_OPEN_FILES, @@ -408,19 +404,15 @@ public class RollingLevelDBTimelineStore extends AbstractService implements .add(writeReverseOrderedLong(revStartTime)).add(entityId) .getBytesForLookup(); - DBIterator iterator = null; - try { - DB db = entitydb.getDBForStartTime(revStartTime); - if (db == null) { - return null; - } - iterator = db.iterator(); + DB db = entitydb.getDBForStartTime(revStartTime); + if (db == null) { + return null; + } + try (DBIterator iterator = db.iterator()) { iterator.seek(prefix); return getEntity(entityId, entityType, revStartTime, fields, iterator, prefix, prefix.length); - } finally { - IOUtils.cleanup(LOG, iterator); } } @@ -533,62 +525,61 @@ public class RollingLevelDBTimelineStore extends AbstractService implements o2.length); } }); - DBIterator iterator = null; - try { + // look up start times for the specified entities // skip entities with no start time - for (String entityId : entityIds) { - byte[] startTime = getStartTime(entityId, entityType); - if (startTime != null) { - List entities = startTimeMap.get(startTime); - if (entities == null) { - entities = new ArrayList(); - startTimeMap.put(startTime, entities); - } - entities.add(new EntityIdentifier(entityId, entityType)); + for (String entityId : entityIds) { + byte[] startTime = getStartTime(entityId, entityType); + if (startTime != null) { + List entities = startTimeMap.get(startTime); + if (entities == null) { + entities = new ArrayList(); + startTimeMap.put(startTime, entities); } + entities.add(new EntityIdentifier(entityId, entityType)); } - for (Entry> entry : startTimeMap + } + for (Entry> entry : startTimeMap .entrySet()) { - // look up the events matching the given parameters (limit, - // start time, end time, event types) for entities whose start times - // were found and add the entities to the return list - byte[] revStartTime = entry.getKey(); - for (EntityIdentifier entityIdentifier : entry.getValue()) { - EventsOfOneEntity entity = new EventsOfOneEntity(); - entity.setEntityId(entityIdentifier.getId()); - entity.setEntityType(entityType); - events.addEvent(entity); - KeyBuilder kb = KeyBuilder.newInstance().add(entityType) - .add(revStartTime).add(entityIdentifier.getId()) - .add(EVENTS_COLUMN); - byte[] prefix = kb.getBytesForLookup(); - if (windowEnd == null) { - windowEnd = Long.MAX_VALUE; - } - byte[] revts = writeReverseOrderedLong(windowEnd); - kb.add(revts); - byte[] first = kb.getBytesForLookup(); - byte[] last = null; - if (windowStart != null) { - last = KeyBuilder.newInstance().add(prefix) - .add(writeReverseOrderedLong(windowStart)).getBytesForLookup(); - } - if (limit == null) { - limit = DEFAULT_LIMIT; - } - DB db = entitydb.getDBForStartTime(readReverseOrderedLong( - revStartTime, 0)); - if (db == null) { - continue; - } - iterator = db.iterator(); + // look up the events matching the given parameters (limit, + // start time, end time, event types) for entities whose start times + // were found and add the entities to the return list + byte[] revStartTime = entry.getKey(); + for (EntityIdentifier entityIdentifier : entry.getValue()) { + EventsOfOneEntity entity = new EventsOfOneEntity(); + entity.setEntityId(entityIdentifier.getId()); + entity.setEntityType(entityType); + events.addEvent(entity); + KeyBuilder kb = KeyBuilder.newInstance().add(entityType) + .add(revStartTime).add(entityIdentifier.getId()) + .add(EVENTS_COLUMN); + byte[] prefix = kb.getBytesForLookup(); + if (windowEnd == null) { + windowEnd = Long.MAX_VALUE; + } + byte[] revts = writeReverseOrderedLong(windowEnd); + kb.add(revts); + byte[] first = kb.getBytesForLookup(); + byte[] last = null; + if (windowStart != null) { + last = KeyBuilder.newInstance().add(prefix) + .add(writeReverseOrderedLong(windowStart)).getBytesForLookup(); + } + if (limit == null) { + limit = DEFAULT_LIMIT; + } + DB db = entitydb.getDBForStartTime(readReverseOrderedLong( + revStartTime, 0)); + if (db == null) { + continue; + } + try (DBIterator iterator = db.iterator()) { for (iterator.seek(first); entity.getEvents().size() < limit && iterator.hasNext(); iterator.next()) { byte[] key = iterator.peekNext().getKey(); if (!prefixMatches(prefix, prefix.length, key) || (last != null && WritableComparator.compareBytes(key, 0, - key.length, last, 0, last.length) > 0)) { + key.length, last, 0, last.length) > 0)) { break; } TimelineEvent event = getEntityEvent(eventType, key, prefix.length, @@ -599,8 +590,6 @@ public class RollingLevelDBTimelineStore extends AbstractService implements } } } - } finally { - IOUtils.cleanup(LOG, iterator); } return events; } @@ -657,66 +646,64 @@ public class RollingLevelDBTimelineStore extends AbstractService implements Long limit, Long starttime, Long endtime, String fromId, Long fromTs, Collection secondaryFilters, EnumSet fields, CheckAcl checkAcl, boolean usingPrimaryFilter) throws IOException { - DBIterator iterator = null; - try { - KeyBuilder kb = KeyBuilder.newInstance().add(base).add(entityType); - // only db keys matching the prefix (base + entity type) will be parsed - byte[] prefix = kb.getBytesForLookup(); - if (endtime == null) { - // if end time is null, place no restriction on end time - endtime = Long.MAX_VALUE; - } + KeyBuilder kb = KeyBuilder.newInstance().add(base).add(entityType); + // only db keys matching the prefix (base + entity type) will be parsed + byte[] prefix = kb.getBytesForLookup(); + if (endtime == null) { + // if end time is null, place no restriction on end time + endtime = Long.MAX_VALUE; + } - // Sanitize the fields parameter - if (fields == null) { - fields = EnumSet.allOf(Field.class); - } + // Sanitize the fields parameter + if (fields == null) { + fields = EnumSet.allOf(Field.class); + } - // construct a first key that will be seeked to using end time or fromId - long firstStartTime = Long.MAX_VALUE; - byte[] first = null; - if (fromId != null) { - Long fromIdStartTime = getStartTimeLong(fromId, entityType); - if (fromIdStartTime == null) { - // no start time for provided id, so return empty entities - return new TimelineEntities(); - } - if (fromIdStartTime <= endtime) { - // if provided id's start time falls before the end of the window, - // use it to construct the seek key - firstStartTime = fromIdStartTime; - first = kb.add(writeReverseOrderedLong(fromIdStartTime)).add(fromId) - .getBytesForLookup(); - } + // construct a first key that will be seeked to using end time or fromId + long firstStartTime = Long.MAX_VALUE; + byte[] first = null; + if (fromId != null) { + Long fromIdStartTime = getStartTimeLong(fromId, entityType); + if (fromIdStartTime == null) { + // no start time for provided id, so return empty entities + return new TimelineEntities(); } - // if seek key wasn't constructed using fromId, construct it using end ts - if (first == null) { - firstStartTime = endtime; - first = kb.add(writeReverseOrderedLong(endtime)).getBytesForLookup(); - } - byte[] last = null; - if (starttime != null) { - // if start time is not null, set a last key that will not be - // iterated past - last = KeyBuilder.newInstance().add(base).add(entityType) - .add(writeReverseOrderedLong(starttime)).getBytesForLookup(); - } - if (limit == null) { - // if limit is not specified, use the default - limit = DEFAULT_LIMIT; + if (fromIdStartTime <= endtime) { + // if provided id's start time falls before the end of the window, + // use it to construct the seek key + firstStartTime = fromIdStartTime; + first = kb.add(writeReverseOrderedLong(fromIdStartTime)).add(fromId) + .getBytesForLookup(); } + } + // if seek key wasn't constructed using fromId, construct it using end ts + if (first == null) { + firstStartTime = endtime; + first = kb.add(writeReverseOrderedLong(endtime)).getBytesForLookup(); + } + byte[] last = null; + if (starttime != null) { + // if start time is not null, set a last key that will not be + // iterated past + last = KeyBuilder.newInstance().add(base).add(entityType) + .add(writeReverseOrderedLong(starttime)).getBytesForLookup(); + } + if (limit == null) { + // if limit is not specified, use the default + limit = DEFAULT_LIMIT; + } - TimelineEntities entities = new TimelineEntities(); - RollingLevelDB rollingdb = null; - if (usingPrimaryFilter) { - rollingdb = indexdb; - } else { - rollingdb = entitydb; - } + TimelineEntities entities = new TimelineEntities(); + RollingLevelDB rollingdb = null; + if (usingPrimaryFilter) { + rollingdb = indexdb; + } else { + rollingdb = entitydb; + } - DB db = rollingdb.getDBForStartTime(firstStartTime); - while (entities.getEntities().size() < limit && db != null) { - iterator = db.iterator(); + DB db = rollingdb.getDBForStartTime(firstStartTime); + while (entities.getEntities().size() < limit && db != null) { + try (DBIterator iterator = db.iterator()) { iterator.seek(first); // iterate until one of the following conditions is met: limit is @@ -726,7 +713,7 @@ public class RollingLevelDBTimelineStore extends AbstractService implements byte[] key = iterator.peekNext().getKey(); if (!prefixMatches(prefix, prefix.length, key) || (last != null && WritableComparator.compareBytes(key, 0, - key.length, last, 0, last.length) > 0)) { + key.length, last, 0, last.length) > 0)) { break; } // read the start time and entity id from the current key @@ -814,10 +801,8 @@ public class RollingLevelDBTimelineStore extends AbstractService implements } db = rollingdb.getPreviousDB(db); } - return entities; - } finally { - IOUtils.cleanup(LOG, iterator); } + return entities; } /** @@ -1459,15 +1444,14 @@ public class RollingLevelDBTimelineStore extends AbstractService implements long startTimesCount = 0; WriteBatch writeBatch = null; - DBIterator iterator = null; - try { - writeBatch = starttimedb.createWriteBatch(); - ReadOptions readOptions = new ReadOptions(); - readOptions.fillCache(false); - iterator = starttimedb.iterator(readOptions); + ReadOptions readOptions = new ReadOptions(); + readOptions.fillCache(false); + try (DBIterator iterator = starttimedb.iterator(readOptions)) { + // seek to the first start time entry iterator.seekToFirst(); + writeBatch = starttimedb.createWriteBatch(); // evaluate each start time entry to see if it needs to be evicted or not while (iterator.hasNext()) { @@ -1513,7 +1497,6 @@ public class RollingLevelDBTimelineStore extends AbstractService implements + " start time entities earlier than " + minStartTime); } finally { IOUtils.cleanup(LOG, writeBatch); - IOUtils.cleanup(LOG, iterator); } return startTimesCount; } @@ -1598,11 +1581,9 @@ public class RollingLevelDBTimelineStore extends AbstractService implements // TODO: make data retention work with the domain data as well @Override public void put(TimelineDomain domain) throws IOException { - WriteBatch domainWriteBatch = null; - WriteBatch ownerWriteBatch = null; - try { - domainWriteBatch = domaindb.createWriteBatch(); - ownerWriteBatch = ownerdb.createWriteBatch(); + try (WriteBatch domainWriteBatch = domaindb.createWriteBatch(); + WriteBatch ownerWriteBatch = ownerdb.createWriteBatch();) { + if (domain.getId() == null || domain.getId().length() == 0) { throw new IllegalArgumentException("Domain doesn't have an ID"); } @@ -1682,9 +1663,6 @@ public class RollingLevelDBTimelineStore extends AbstractService implements ownerWriteBatch.put(ownerLookupEntryKey, timestamps); domaindb.write(domainWriteBatch); ownerdb.write(ownerWriteBatch); - } finally { - IOUtils.cleanup(LOG, domainWriteBatch); - IOUtils.cleanup(LOG, ownerWriteBatch); } } @@ -1709,26 +1687,21 @@ public class RollingLevelDBTimelineStore extends AbstractService implements @Override public TimelineDomain getDomain(String domainId) throws IOException { - DBIterator iterator = null; - try { + try (DBIterator iterator = domaindb.iterator()) { byte[] prefix = KeyBuilder.newInstance().add(domainId) .getBytesForLookup(); - iterator = domaindb.iterator(); iterator.seek(prefix); return getTimelineDomain(iterator, domainId, prefix); - } finally { - IOUtils.cleanup(LOG, iterator); } } @Override public TimelineDomains getDomains(String owner) throws IOException { - DBIterator iterator = null; - try { + try (DBIterator iterator = ownerdb.iterator()) { byte[] prefix = KeyBuilder.newInstance().add(owner).getBytesForLookup(); + iterator.seek(prefix); List domains = new ArrayList(); - for (iterator = ownerdb.iterator(), iterator.seek(prefix); iterator - .hasNext();) { + while (iterator.hasNext()) { byte[] key = iterator.peekNext().getKey(); if (!prefixMatches(prefix, prefix.length, key)) { break; @@ -1761,8 +1734,6 @@ public class RollingLevelDBTimelineStore extends AbstractService implements TimelineDomains domainsToReturn = new TimelineDomains(); domainsToReturn.addDomains(domains); return domainsToReturn; - } finally { - IOUtils.cleanup(LOG, iterator); } } From 063b513b1c10987461caab3d26c8543c6e657bf7 Mon Sep 17 00:00:00 2001 From: Varun Saxena Date: Wed, 29 Mar 2017 03:48:03 +0530 Subject: [PATCH 150/188] YARN-6357. Implement putEntitiesAsync API in TimelineCollector (Haibo Chen via Varun Saxena) --- .../collector/TimelineCollector.java | 31 +++++++-- .../TimelineCollectorWebService.java | 12 ++-- .../collector/TestTimelineCollector.java | 63 +++++++++++++++++++ 3 files changed, 96 insertions(+), 10 deletions(-) diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/collector/TimelineCollector.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/collector/TimelineCollector.java index 2fc30333133..353066bd775 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/collector/TimelineCollector.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/collector/TimelineCollector.java @@ -133,19 +133,35 @@ public abstract class TimelineCollector extends CompositeService { public TimelineWriteResponse putEntities(TimelineEntities entities, UserGroupInformation callerUgi) throws IOException { if (LOG.isDebugEnabled()) { - LOG.debug("SUCCESS - TIMELINE V2 PROTOTYPE"); LOG.debug("putEntities(entities=" + entities + ", callerUgi=" + callerUgi + ")"); } - TimelineCollectorContext context = getTimelineEntityContext(); + TimelineWriteResponse response = writeTimelineEntities(entities); + flushBufferedTimelineEntities(); + + return response; + } + + private TimelineWriteResponse writeTimelineEntities( + TimelineEntities entities) throws IOException { // Update application metrics for aggregation updateAggregateStatus(entities, aggregationGroups, getEntityTypesSkipAggregation()); + final TimelineCollectorContext context = getTimelineEntityContext(); return writer.write(context.getClusterId(), context.getUserId(), - context.getFlowName(), context.getFlowVersion(), context.getFlowRunId(), - context.getAppId(), entities); + context.getFlowName(), context.getFlowVersion(), + context.getFlowRunId(), context.getAppId(), entities); + } + + /** + * Flush buffered timeline entities, if any. + * @throws IOException if there is any exception encountered while + * flushing buffered entities. + */ + private void flushBufferedTimelineEntities() throws IOException { + writer.flush(); } /** @@ -158,14 +174,17 @@ public abstract class TimelineCollector extends CompositeService { * * @param entities entities to post * @param callerUgi the caller UGI + * @throws IOException if there is any exception encounted while putting + * entities. */ public void putEntitiesAsync(TimelineEntities entities, - UserGroupInformation callerUgi) { - // TODO implement + UserGroupInformation callerUgi) throws IOException { if (LOG.isDebugEnabled()) { LOG.debug("putEntitiesAsync(entities=" + entities + ", callerUgi=" + callerUgi + ")"); } + + writeTimelineEntities(entities); } /** diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/collector/TimelineCollectorWebService.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/collector/TimelineCollectorWebService.java index f36c63609c2..fe04b7afc5b 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/collector/TimelineCollectorWebService.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/collector/TimelineCollectorWebService.java @@ -152,9 +152,6 @@ public class TimelineCollectorWebService { throw new ForbiddenException(msg); } - // TODO how to express async posts and handle them - boolean isAsync = async != null && async.trim().equalsIgnoreCase("true"); - try { ApplicationId appID = parseApplicationId(appId); if (appID == null) { @@ -169,7 +166,14 @@ public class TimelineCollectorWebService { throw new NotFoundException(); // different exception? } - collector.putEntities(processTimelineEntities(entities), callerUgi); + boolean isAsync = async != null && async.trim().equalsIgnoreCase("true"); + if (isAsync) { + collector.putEntitiesAsync( + processTimelineEntities(entities), callerUgi); + } else { + collector.putEntities(processTimelineEntities(entities), callerUgi); + } + return Response.ok().build(); } catch (Exception e) { LOG.error("Error putting entities", e); diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/test/java/org/apache/hadoop/yarn/server/timelineservice/collector/TestTimelineCollector.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/test/java/org/apache/hadoop/yarn/server/timelineservice/collector/TestTimelineCollector.java index 5b4dc50deed..a55f2276a96 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/test/java/org/apache/hadoop/yarn/server/timelineservice/collector/TestTimelineCollector.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/test/java/org/apache/hadoop/yarn/server/timelineservice/collector/TestTimelineCollector.java @@ -18,17 +18,27 @@ package org.apache.hadoop.yarn.server.timelineservice.collector; +import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.yarn.api.records.timelineservice.TimelineMetricOperation; import org.apache.hadoop.yarn.api.records.timelineservice.TimelineEntities; import org.apache.hadoop.yarn.api.records.timelineservice.TimelineEntity; import org.apache.hadoop.yarn.api.records.timelineservice.TimelineMetric; +import org.apache.hadoop.yarn.server.timelineservice.storage.TimelineWriter; import org.junit.Test; +import java.io.IOException; import java.util.HashSet; import java.util.Set; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; public class TestTimelineCollector { @@ -124,4 +134,57 @@ public class TestTimelineCollector { } } + + /** + * Test TimelineCollector's interaction with TimelineWriter upon + * putEntity() calls. + */ + @Test + public void testPutEntity() throws IOException { + TimelineWriter writer = mock(TimelineWriter.class); + TimelineCollector collector = new TimelineCollectorForTest(writer); + + TimelineEntities entities = generateTestEntities(1, 1); + collector.putEntities( + entities, UserGroupInformation.createRemoteUser("test-user")); + + verify(writer, times(1)).write( + anyString(), anyString(), anyString(), anyString(), anyLong(), + anyString(), any(TimelineEntities.class)); + verify(writer, times(1)).flush(); + } + + /** + * Test TimelineCollector's interaction with TimelineWriter upon + * putEntityAsync() calls. + */ + @Test + public void testPutEntityAsync() throws IOException { + TimelineWriter writer = mock(TimelineWriter.class); + TimelineCollector collector = new TimelineCollectorForTest(writer); + + TimelineEntities entities = generateTestEntities(1, 1); + collector.putEntitiesAsync( + entities, UserGroupInformation.createRemoteUser("test-user")); + + verify(writer, times(1)).write( + anyString(), anyString(), anyString(), anyString(), anyLong(), + anyString(), any(TimelineEntities.class)); + verify(writer, never()).flush(); + } + + private static class TimelineCollectorForTest extends TimelineCollector { + private final TimelineCollectorContext context = + new TimelineCollectorContext(); + + TimelineCollectorForTest(TimelineWriter writer) { + super("TimelineCollectorForTest"); + setWriter(writer); + } + + @Override + public TimelineCollectorContext getTimelineEntityContext() { + return context; + } + } } From 82fb9ce8df7a534a4cbcec624d7b6c0b33e79123 Mon Sep 17 00:00:00 2001 From: Akira Ajisaka Date: Wed, 29 Mar 2017 10:35:30 +0900 Subject: [PATCH 151/188] YARN-6329. Remove unnecessary TODO comment from AppLogAggregatorImpl.java. Contributed by victor bertschinger. --- .../containermanager/logaggregation/AppLogAggregatorImpl.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/logaggregation/AppLogAggregatorImpl.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/logaggregation/AppLogAggregatorImpl.java index 065854e98df..d70acc940e4 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/logaggregation/AppLogAggregatorImpl.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/logaggregation/AppLogAggregatorImpl.java @@ -573,8 +573,6 @@ public class AppLogAggregatorImpl implements AppLogAggregator { (remoteNodeLogFileForApp.getName() + LogAggregationUtils.TMP_FILE_SUFFIX)); } - // TODO: The condition: containerId.getId() == 1 to determine an AM container - // is not always true. private boolean shouldUploadLogs(ContainerLogContext logContext) { return logAggPolicy.shouldDoLogAggregation(logContext); } From 0e6f8e4bc6642f90dc7b33848bfb1129ec20ee49 Mon Sep 17 00:00:00 2001 From: Andrew Wang Date: Tue, 28 Mar 2017 22:14:03 -0700 Subject: [PATCH 152/188] HDFS-10971. Distcp should not copy replication factor if source file is erasure coded. Contributed by Manoj Govindassamy. --- .../hadoop/tools/CopyListingFileStatus.java | 4 + .../mapred/RetriableFileCopyCommand.java | 3 + .../apache/hadoop/tools/util/DistCpUtils.java | 9 +- .../tools/TestCopyListingFileStatus.java | 1 + .../hadoop/tools/util/TestDistCpUtils.java | 123 +++++++++++++++++- 5 files changed, 137 insertions(+), 3 deletions(-) diff --git a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/CopyListingFileStatus.java b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/CopyListingFileStatus.java index 2b1e7e4ce47..00d4b325053 100644 --- a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/CopyListingFileStatus.java +++ b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/CopyListingFileStatus.java @@ -159,6 +159,10 @@ public final class CopyListingFileStatus implements Writable { return permission; } + public boolean isErasureCoded() { + return getPermission().getErasureCodedBit(); + } + /** * Returns the full logical ACL. * diff --git a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/RetriableFileCopyCommand.java b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/RetriableFileCopyCommand.java index d1cdfdd5485..06acd78a8a1 100644 --- a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/RetriableFileCopyCommand.java +++ b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/RetriableFileCopyCommand.java @@ -167,6 +167,9 @@ public class RetriableFileCopyCommand extends RetriableCommand { FsPermission.getUMask(targetFS.getConf())); final OutputStream outStream; if (action == FileAction.OVERWRITE) { + // If there is an erasure coding policy set on the target directory, + // files will be written to the target directory using the same EC policy. + // The replication factor of the source file is ignored and not preserved. final short repl = getReplicationFactor(fileAttributes, source, targetFS, targetPath); final long blockSize = getBlockSize(fileAttributes, source, diff --git a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/util/DistCpUtils.java b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/util/DistCpUtils.java index c308e6f1f90..76bc4c56268 100644 --- a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/util/DistCpUtils.java +++ b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/util/DistCpUtils.java @@ -236,8 +236,13 @@ public class DistCpUtils { } } - if (attributes.contains(FileAttribute.REPLICATION) && !targetFileStatus.isDirectory() && - (srcFileStatus.getReplication() != targetFileStatus.getReplication())) { + // The replication factor can only be preserved for replicated files. + // It is ignored when either the source or target file are erasure coded. + if (attributes.contains(FileAttribute.REPLICATION) && + !targetFileStatus.isDirectory() && + !targetFileStatus.isErasureCoded() && + !srcFileStatus.isErasureCoded() && + srcFileStatus.getReplication() != targetFileStatus.getReplication()) { targetFS.setReplication(path, srcFileStatus.getReplication()); } diff --git a/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestCopyListingFileStatus.java b/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestCopyListingFileStatus.java index f512ef6d9d8..8efc5cf9942 100644 --- a/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestCopyListingFileStatus.java +++ b/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestCopyListingFileStatus.java @@ -62,6 +62,7 @@ public class TestCopyListingFileStatus { assertEquals(stat.getOwner(), clfs.getOwner()); assertEquals(stat.getGroup(), clfs.getGroup()); assertEquals(stat.getPath(), clfs.getPath()); + assertEquals(stat.isErasureCoded(), clfs.isErasureCoded()); } } diff --git a/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/util/TestDistCpUtils.java b/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/util/TestDistCpUtils.java index 8c79becfa0b..c42e5465680 100644 --- a/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/util/TestDistCpUtils.java +++ b/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/util/TestDistCpUtils.java @@ -18,6 +18,10 @@ package org.apache.hadoop.tools.util; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; + import java.io.IOException; import java.io.OutputStream; import java.util.EnumSet; @@ -31,11 +35,15 @@ 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.MiniDFSCluster; +import org.apache.hadoop.hdfs.server.namenode.INodeFile; +import org.apache.hadoop.hdfs.tools.ECAdmin; import org.apache.hadoop.io.IOUtils; import org.apache.hadoop.tools.CopyListingFileStatus; import org.apache.hadoop.tools.DistCpOptionSwitch; import org.apache.hadoop.tools.DistCpOptions.FileAttribute; +import org.apache.hadoop.util.ToolRunner; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; @@ -52,8 +60,10 @@ public class TestDistCpUtils { @BeforeClass public static void create() throws IOException { + config.set(DFSConfigKeys.DFS_NAMENODE_EC_POLICIES_ENABLED_KEY, + "XOR-2-1-64k"); cluster = new MiniDFSCluster.Builder(config) - .numDataNodes(1) + .numDataNodes(2) .format(true) .build(); } @@ -539,6 +549,117 @@ public class TestDistCpUtils { Assert.assertTrue(srcStatus.getReplication() == dstStatus.getReplication()); } + @Test (timeout = 60000) + public void testReplFactorNotPreservedOnErasureCodedFile() throws Exception { + FileSystem fs = FileSystem.get(config); + + // Case 1: Verify replication attribute not preserved when the source + // file is erasure coded and the target file is replicated. + Path srcECDir = new Path("/tmp/srcECDir"); + Path srcECFile = new Path(srcECDir, "srcECFile"); + Path dstReplDir = new Path("/tmp/dstReplDir"); + Path dstReplFile = new Path(dstReplDir, "destReplFile"); + fs.mkdirs(srcECDir); + fs.mkdirs(dstReplDir); + String[] args = {"-setPolicy", "-path", "/tmp/srcECDir", + "-policy", "XOR-2-1-64k"}; + int res = ToolRunner.run(config, new ECAdmin(config), args); + assertEquals("Setting EC policy should succeed!", 0, res); + verifyReplFactorNotPreservedOnErasureCodedFile(srcECFile, true, + dstReplFile, false); + + // Case 2: Verify replication attribute not preserved when the source + // file is replicated and the target file is erasure coded. + Path srcReplDir = new Path("/tmp/srcReplDir"); + Path srcReplFile = new Path(srcReplDir, "srcReplFile"); + Path dstECDir = new Path("/tmp/dstECDir"); + Path dstECFile = new Path(dstECDir, "destECFile"); + fs.mkdirs(srcReplDir); + fs.mkdirs(dstECDir); + args = new String[]{"-setPolicy", "-path", "/tmp/dstECDir", + "-policy", "XOR-2-1-64k"}; + res = ToolRunner.run(config, new ECAdmin(config), args); + assertEquals("Setting EC policy should succeed!", 0, res); + verifyReplFactorNotPreservedOnErasureCodedFile(srcReplFile, + false, dstECFile, true); + + // Case 3: Verify replication attribute not altered from the default + // INodeFile.DEFAULT_REPL_FOR_STRIPED_BLOCKS when both source and + // target files are erasure coded. + verifyReplFactorNotPreservedOnErasureCodedFile(srcECFile, + true, dstECFile, true); + } + + private void verifyReplFactorNotPreservedOnErasureCodedFile(Path srcFile, + boolean isSrcEC, Path dstFile, boolean isDstEC) throws Exception { + FileSystem fs = FileSystem.get(config); + createFile(fs, srcFile); + CopyListingFileStatus srcStatus = new CopyListingFileStatus( + fs.getFileStatus(srcFile)); + if (isSrcEC) { + assertTrue(srcFile + "should be erasure coded!", + srcStatus.isErasureCoded()); + assertEquals(INodeFile.DEFAULT_REPL_FOR_STRIPED_BLOCKS, + srcStatus.getReplication()); + } else { + assertEquals("Unexpected replication factor for " + srcFile, + fs.getDefaultReplication(srcFile), srcStatus.getReplication()); + } + + createFile(fs, dstFile); + CopyListingFileStatus dstStatus = new CopyListingFileStatus( + fs.getFileStatus(dstFile)); + if (isDstEC) { + assertTrue(dstFile + "should be erasure coded!", + dstStatus.isErasureCoded()); + assertEquals("Unexpected replication factor for erasure coded file!", + INodeFile.DEFAULT_REPL_FOR_STRIPED_BLOCKS, + dstStatus.getReplication()); + } else { + assertEquals("Unexpected replication factor for " + dstFile, + fs.getDefaultReplication(dstFile), dstStatus.getReplication()); + } + + // Let srcFile and dstFile differ on their FileAttribute + fs.setPermission(srcFile, fullPerm); + fs.setOwner(srcFile, "ec", "ec-group"); + fs.setTimes(srcFile, 0, 0); + + fs.setPermission(dstFile, noPerm); + fs.setOwner(dstFile, "normal", "normal-group"); + fs.setTimes(dstFile, 100, 100); + + // Running preserve operations only for replication attribute + srcStatus = new CopyListingFileStatus(fs.getFileStatus(srcFile)); + EnumSet attributes = EnumSet.of(FileAttribute.REPLICATION); + DistCpUtils.preserve(fs, dstFile, srcStatus, attributes, false); + dstStatus = new CopyListingFileStatus(fs.getFileStatus(dstFile)); + + assertFalse("Permission for " + srcFile + " and " + dstFile + + " should not be same after preserve only for replication attr!", + srcStatus.getPermission().equals(dstStatus.getPermission())); + assertFalse("File ownership should not match!", + srcStatus.getOwner().equals(dstStatus.getOwner())); + assertFalse(srcStatus.getGroup().equals(dstStatus.getGroup())); + assertFalse(srcStatus.getAccessTime() == dstStatus.getAccessTime()); + assertFalse( + srcStatus.getModificationTime() == dstStatus.getModificationTime()); + if (isDstEC) { + assertEquals("Unexpected replication factor for erasure coded file!", + INodeFile.DEFAULT_REPL_FOR_STRIPED_BLOCKS, + dstStatus.getReplication()); + } else { + assertEquals(dstFile + " replication factor should be same as dst " + + "filesystem!", fs.getDefaultReplication(dstFile), + dstStatus.getReplication()); + } + if (!isSrcEC || !isDstEC) { + assertFalse(dstFile + " replication factor should not be " + + "same as " + srcFile, + srcStatus.getReplication() == dstStatus.getReplication()); + } + } + @Test public void testPreserveTimestampOnFile() throws IOException { FileSystem fs = FileSystem.get(config); From 84d787b9d51196010495d51dc5ebf66c01c340ab Mon Sep 17 00:00:00 2001 From: Rakesh Radhakrishnan Date: Wed, 29 Mar 2017 11:41:48 +0530 Subject: [PATCH 153/188] HDFS-11541. Call RawErasureEncoder and RawErasureDecoder release() methods. Contributed by SammiChen. --- .../hadoop/hdfs/DFSStripedInputStream.java | 20 +++++---- .../hadoop/hdfs/DFSStripedOutputStream.java | 1 + .../StripedBlockChecksumReconstructor.java | 42 ++++++++++--------- .../StripedBlockReconstructor.java | 1 + .../erasurecode/StripedReconstructor.java | 6 +++ 5 files changed, 43 insertions(+), 27 deletions(-) diff --git a/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/DFSStripedInputStream.java b/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/DFSStripedInputStream.java index 922f74eaecc..07e660934db 100644 --- a/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/DFSStripedInputStream.java +++ b/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/DFSStripedInputStream.java @@ -177,14 +177,18 @@ public class DFSStripedInputStream extends DFSInputStream { @Override public synchronized void close() throws IOException { - super.close(); - if (curStripeBuf != null) { - BUFFER_POOL.putBuffer(curStripeBuf); - curStripeBuf = null; - } - if (parityBuf != null) { - BUFFER_POOL.putBuffer(parityBuf); - parityBuf = null; + try { + super.close(); + } finally { + if (curStripeBuf != null) { + BUFFER_POOL.putBuffer(curStripeBuf); + curStripeBuf = null; + } + if (parityBuf != null) { + BUFFER_POOL.putBuffer(parityBuf); + parityBuf = null; + } + decoder.release(); } } diff --git a/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/DFSStripedOutputStream.java b/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/DFSStripedOutputStream.java index 52fc5ebf71e..22b30e93063 100644 --- a/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/DFSStripedOutputStream.java +++ b/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/DFSStripedOutputStream.java @@ -1033,6 +1033,7 @@ public class DFSStripedOutputStream extends DFSOutputStream { setClosed(); // shutdown executor of flushAll tasks flushAllExecutor.shutdownNow(); + encoder.release(); } } diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/erasurecode/StripedBlockChecksumReconstructor.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/erasurecode/StripedBlockChecksumReconstructor.java index 95556189903..69173173fee 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/erasurecode/StripedBlockChecksumReconstructor.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/erasurecode/StripedBlockChecksumReconstructor.java @@ -75,29 +75,33 @@ public class StripedBlockChecksumReconstructor extends StripedReconstructor { public void reconstruct() throws IOException { MessageDigest digester = MD5Hash.getDigester(); long maxTargetLength = getMaxTargetLength(); - while (requestedLen > 0 && getPositionInBlock() < maxTargetLength) { - long remaining = maxTargetLength - getPositionInBlock(); - final int toReconstructLen = (int) Math - .min(getStripedReader().getBufferSize(), remaining); - // step1: read from minimum source DNs required for reconstruction. - // The returned success list is the source DNs we do real read from - getStripedReader().readMinimumSources(toReconstructLen); + try { + while (requestedLen > 0 && getPositionInBlock() < maxTargetLength) { + long remaining = maxTargetLength - getPositionInBlock(); + final int toReconstructLen = (int) Math + .min(getStripedReader().getBufferSize(), remaining); + // step1: read from minimum source DNs required for reconstruction. + // The returned success list is the source DNs we do real read from + getStripedReader().readMinimumSources(toReconstructLen); - // step2: decode to reconstruct targets - reconstructTargets(toReconstructLen); + // step2: decode to reconstruct targets + reconstructTargets(toReconstructLen); - // step3: calculate checksum - checksumDataLen += checksumWithTargetOutput(targetBuffer.array(), - toReconstructLen, digester); + // step3: calculate checksum + checksumDataLen += checksumWithTargetOutput(targetBuffer.array(), + toReconstructLen, digester); - updatePositionInBlock(toReconstructLen); - requestedLen -= toReconstructLen; - clearBuffers(); + updatePositionInBlock(toReconstructLen); + requestedLen -= toReconstructLen; + clearBuffers(); + } + + byte[] digest = digester.digest(); + md5 = new MD5Hash(digest); + md5.write(checksumWriter); + } finally { + cleanup(); } - - byte[] digest = digester.digest(); - md5 = new MD5Hash(digest); - md5.write(checksumWriter); } private long checksumWithTargetOutput(byte[] outputData, int toReconstructLen, diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/erasurecode/StripedBlockReconstructor.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/erasurecode/StripedBlockReconstructor.java index a1da536ba58..1119bbbd230 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/erasurecode/StripedBlockReconstructor.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/erasurecode/StripedBlockReconstructor.java @@ -74,6 +74,7 @@ class StripedBlockReconstructor extends StripedReconstructor metrics.incrECReconstructionBytesWritten(getBytesWritten()); getStripedReader().close(); stripedWriter.close(); + cleanup(); } } diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/erasurecode/StripedReconstructor.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/erasurecode/StripedReconstructor.java index cd17864c943..b8433c7b6c3 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/erasurecode/StripedReconstructor.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/erasurecode/StripedReconstructor.java @@ -253,6 +253,12 @@ abstract class StripedReconstructor { return decoder; } + void cleanup() { + if (decoder != null) { + decoder.release(); + } + } + StripedReader getStripedReader() { return stripedReader; } From 523f467d939d80e2bc162e1f47be497109783061 Mon Sep 17 00:00:00 2001 From: Jonathan Eagles Date: Wed, 29 Mar 2017 10:12:02 -0500 Subject: [PATCH 154/188] HADOOP-14216. Improve Configuration XML Parsing Performance (jeagles) --- hadoop-common-project/hadoop-common/pom.xml | 10 + .../org/apache/hadoop/conf/Configuration.java | 311 +++++++++++------- .../apache/hadoop/conf/TestConfiguration.java | 62 +++- hadoop-project/pom.xml | 10 + 4 files changed, 263 insertions(+), 130 deletions(-) diff --git a/hadoop-common-project/hadoop-common/pom.xml b/hadoop-common-project/hadoop-common/pom.xml index 961c9028393..ee82df00b44 100644 --- a/hadoop-common-project/hadoop-common/pom.xml +++ b/hadoop-common-project/hadoop-common/pom.xml @@ -313,6 +313,16 @@ com.fasterxml.jackson.core jackson-databind + + org.codehaus.woodstox + stax2-api + compile + + + com.fasterxml + aalto-xml + compile + diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/conf/Configuration.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/conf/Configuration.java index da4f6ac6b3f..36845e393bd 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/conf/Configuration.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/conf/Configuration.java @@ -18,6 +18,7 @@ package org.apache.hadoop.conf; +import com.fasterxml.aalto.stax.InputFactoryImpl; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerator; import com.google.common.annotations.VisibleForTesting; @@ -65,9 +66,11 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; -import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; +import javax.xml.stream.XMLStreamConstants; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; @@ -93,14 +96,10 @@ import org.apache.hadoop.security.alias.CredentialProviderFactory; import org.apache.hadoop.util.ReflectionUtils; import org.apache.hadoop.util.StringInterner; import org.apache.hadoop.util.StringUtils; -import org.w3c.dom.Attr; -import org.w3c.dom.DOMException; +import org.codehaus.stax2.XMLInputFactory2; +import org.codehaus.stax2.XMLStreamReader2; import org.w3c.dom.Document; import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.w3c.dom.Text; -import org.xml.sax.SAXException; import com.google.common.base.Preconditions; import com.google.common.base.Strings; @@ -280,7 +279,13 @@ public class Configuration implements Iterable>, * the key most recently */ private Map updatingResource; - + + /** + * Specify exact input factory to avoid time finding correct one. + * Factory is reusable across un-synchronized threads once initialized + */ + private static final XMLInputFactory2 factory = new InputFactoryImpl(); + /** * Class to keep the information about the keys which replace the deprecated * ones. @@ -689,7 +694,7 @@ public class Configuration implements Iterable>, addDefaultResource("hadoop-site.xml"); } } - + private Properties properties; private Properties overlay; private ClassLoader classLoader; @@ -2613,8 +2618,8 @@ public class Configuration implements Iterable>, return configMap; } - private Document parse(DocumentBuilder builder, URL url) - throws IOException, SAXException { + private XMLStreamReader parse(URL url) + throws IOException, XMLStreamException { if (!quietmode) { if (LOG.isDebugEnabled()) { LOG.debug("parsing URL " + url); @@ -2630,23 +2635,18 @@ public class Configuration implements Iterable>, // with other users. connection.setUseCaches(false); } - return parse(builder, connection.getInputStream(), url.toString()); + return parse(connection.getInputStream(), url.toString()); } - private Document parse(DocumentBuilder builder, InputStream is, - String systemId) throws IOException, SAXException { + private XMLStreamReader parse(InputStream is, + String systemId) throws IOException, XMLStreamException { if (!quietmode) { LOG.debug("parsing input stream " + is); } if (is == null) { return null; } - try { - return (systemId == null) ? builder.parse(is) : builder.parse(is, - systemId); - } finally { - is.close(); - } + return factory.createXMLStreamReader(systemId, is); } private void loadResources(Properties properties, @@ -2666,37 +2666,20 @@ public class Configuration implements Iterable>, } } - private Resource loadResource(Properties properties, Resource wrapper, boolean quiet) { + private Resource loadResource(Properties properties, + Resource wrapper, boolean quiet) { String name = UNKNOWN_RESOURCE; try { Object resource = wrapper.getResource(); name = wrapper.getName(); - - DocumentBuilderFactory docBuilderFactory - = DocumentBuilderFactory.newInstance(); - //ignore all comments inside the xml file - docBuilderFactory.setIgnoringComments(true); - - //allow includes in the xml file - docBuilderFactory.setNamespaceAware(true); - try { - docBuilderFactory.setXIncludeAware(true); - } catch (UnsupportedOperationException e) { - LOG.error("Failed to set setXIncludeAware(true) for parser " - + docBuilderFactory - + ":" + e, - e); - } - DocumentBuilder builder = docBuilderFactory.newDocumentBuilder(); - Document doc = null; - Element root = null; + XMLStreamReader2 reader = null; boolean returnCachedProperties = false; - + if (resource instanceof URL) { // an URL resource - doc = parse(builder, (URL)resource); + reader = (XMLStreamReader2)parse((URL)resource); } else if (resource instanceof String) { // a CLASSPATH resource URL url = getResource((String)resource); - doc = parse(builder, url); + reader = (XMLStreamReader2)parse(url); } else if (resource instanceof Path) { // a file resource // Can't use FileSystem API or we get an infinite loop // since FileSystem uses Configuration API. Use java.io.File instead. @@ -2706,104 +2689,188 @@ public class Configuration implements Iterable>, if (!quiet) { LOG.debug("parsing File " + file); } - doc = parse(builder, new BufferedInputStream( + reader = (XMLStreamReader2)parse(new BufferedInputStream( new FileInputStream(file)), ((Path)resource).toString()); } } else if (resource instanceof InputStream) { - doc = parse(builder, (InputStream) resource, null); + reader = (XMLStreamReader2)parse((InputStream)resource, null); returnCachedProperties = true; } else if (resource instanceof Properties) { overlay(properties, (Properties)resource); - } else if (resource instanceof Element) { - root = (Element)resource; } - if (root == null) { - if (doc == null) { - if (quiet) { - return null; - } - throw new RuntimeException(resource + " not found"); + if (reader == null) { + if (quiet) { + return null; } - root = doc.getDocumentElement(); + throw new RuntimeException(resource + " not found"); } Properties toAddTo = properties; if(returnCachedProperties) { toAddTo = new Properties(); } - if (!"configuration".equals(root.getTagName())) - LOG.fatal("bad conf file: top-level element not "); - NodeList props = root.getChildNodes(); DeprecationContext deprecations = deprecationContext.get(); - for (int i = 0; i < props.getLength(); i++) { - Node propNode = props.item(i); - if (!(propNode instanceof Element)) - continue; - Element prop = (Element)propNode; - if ("configuration".equals(prop.getTagName())) { - loadResource(toAddTo, new Resource(prop, name), quiet); - continue; - } - if (!"property".equals(prop.getTagName())) - LOG.warn("bad conf file: element not "); - String attr = null; - String value = null; - boolean finalParameter = false; - LinkedList source = new LinkedList(); + StringBuilder token = new StringBuilder(); + String confName = null; + String confValue = null; + boolean confFinal = false; + boolean fallbackAllowed = false; + boolean fallbackEntered = false; + boolean parseToken = false; + LinkedList confSource = new LinkedList(); - Attr propAttr = prop.getAttributeNode("name"); - if (propAttr != null) - attr = StringInterner.weakIntern(propAttr.getValue()); - propAttr = prop.getAttributeNode("value"); - if (propAttr != null) - value = StringInterner.weakIntern(propAttr.getValue()); - propAttr = prop.getAttributeNode("final"); - if (propAttr != null) - finalParameter = "true".equals(propAttr.getValue()); - propAttr = prop.getAttributeNode("source"); - if (propAttr != null) - source.add(StringInterner.weakIntern(propAttr.getValue())); + while (reader.hasNext()) { + switch (reader.next()) { + case XMLStreamConstants.START_ELEMENT: + switch (reader.getLocalName()) { + case "property": + confName = null; + confValue = null; + confFinal = false; + confSource.clear(); - NodeList fields = prop.getChildNodes(); - for (int j = 0; j < fields.getLength(); j++) { - Node fieldNode = fields.item(j); - if (!(fieldNode instanceof Element)) - continue; - Element field = (Element)fieldNode; - if ("name".equals(field.getTagName()) && field.hasChildNodes()) - attr = StringInterner.weakIntern( - ((Text)field.getFirstChild()).getData().trim()); - if ("value".equals(field.getTagName()) && field.hasChildNodes()) - value = StringInterner.weakIntern( - ((Text)field.getFirstChild()).getData()); - if ("final".equals(field.getTagName()) && field.hasChildNodes()) - finalParameter = "true".equals(((Text)field.getFirstChild()).getData()); - if ("source".equals(field.getTagName()) && field.hasChildNodes()) - source.add(StringInterner.weakIntern( - ((Text)field.getFirstChild()).getData())); - } - source.add(name); - - // Ignore this parameter if it has already been marked as 'final' - if (attr != null) { - if (deprecations.getDeprecatedKeyMap().containsKey(attr)) { - DeprecatedKeyInfo keyInfo = - deprecations.getDeprecatedKeyMap().get(attr); - keyInfo.clearAccessed(); - for (String key:keyInfo.newKeys) { - // update new keys with deprecated key's value - loadProperty(toAddTo, name, key, value, finalParameter, - source.toArray(new String[source.size()])); + // First test for short format configuration + int attrCount = reader.getAttributeCount(); + for (int i = 0; i < attrCount; i++) { + String propertyAttr = reader.getAttributeLocalName(i); + if ("name".equals(propertyAttr)) { + confName = StringInterner.weakIntern( + reader.getAttributeValue(i)); + } else if ("value".equals(propertyAttr)) { + confValue = StringInterner.weakIntern( + reader.getAttributeValue(i)); + } else if ("final".equals(propertyAttr)) { + confFinal = "true".equals(reader.getAttributeValue(i)); + } else if ("source".equals(propertyAttr)) { + confSource.add(StringInterner.weakIntern( + reader.getAttributeValue(i))); + } } + break; + case "name": + case "value": + case "final": + case "source": + parseToken = true; + token.setLength(0); + break; + case "include": + if (!"xi".equals(reader.getPrefix())) { + break; + } + // Determine href for xi:include + String confInclude = null; + attrCount = reader.getAttributeCount(); + for (int i = 0; i < attrCount; i++) { + String attrName = reader.getAttributeLocalName(i); + if ("href".equals(attrName)) { + confInclude = reader.getAttributeValue(i); + } + } + if (confInclude == null) { + break; + } + // Determine if the included resource is a classpath resource + // otherwise fallback to a file resource + // xi:include are treated as inline and retain current source + URL include = getResource(confInclude); + if (include != null) { + Resource classpathResource = new Resource(include, name); + loadResource(properties, classpathResource, quiet); + } else { + File href = new File(confInclude); + if (!href.isAbsolute()) { + // Included resources are relative to the current resource + File baseFile = new File(name).getParentFile(); + href = new File(baseFile, href.getPath()); + } + if (!href.exists()) { + // Resource errors are non-fatal iff there is 1 xi:fallback + fallbackAllowed = true; + break; + } + Resource uriResource = new Resource(href.toURI().toURL(), name); + loadResource(properties, uriResource, quiet); + } + break; + case "fallback": + if (!"xi".equals(reader.getPrefix())) { + break; + } + fallbackEntered = true; + break; + case "configuration": + break; + default: + break; } - else { - loadProperty(toAddTo, name, attr, value, finalParameter, - source.toArray(new String[source.size()])); + break; + + case XMLStreamConstants.CHARACTERS: + if (parseToken) { + char[] text = reader.getTextCharacters(); + token.append(text, reader.getTextStart(), reader.getTextLength()); } + break; + + case XMLStreamConstants.END_ELEMENT: + switch (reader.getLocalName()) { + case "name": + if (token.length() > 0) { + confName = StringInterner.weakIntern(token.toString().trim()); + } + break; + case "value": + if (token.length() > 0) { + confValue = StringInterner.weakIntern(token.toString()); + } + break; + case "final": + confFinal = "true".equals(token.toString()); + break; + case "source": + confSource.add(StringInterner.weakIntern(token.toString())); + break; + case "include": + if (!"xi".equals(reader.getPrefix())) { + break; + } + if (fallbackAllowed && !fallbackEntered) { + throw new IOException("Fetch fail on include with no " + + "fallback while loading '" + name + "'"); + } + fallbackAllowed = false; + fallbackEntered = false; + break; + case "property": + if (confName == null || (!fallbackAllowed && fallbackEntered)) { + break; + } + confSource.add(name); + DeprecatedKeyInfo keyInfo = + deprecations.getDeprecatedKeyMap().get(confName); + if (keyInfo != null) { + keyInfo.clearAccessed(); + for (String key : keyInfo.newKeys) { + // update new keys with deprecated key's value + loadProperty(toAddTo, name, key, confValue, confFinal, + confSource.toArray(new String[confSource.size()])); + } + } else { + loadProperty(toAddTo, name, confName, confValue, confFinal, + confSource.toArray(new String[confSource.size()])); + } + break; + default: + break; + } + default: + break; } } - + reader.close(); + if (returnCachedProperties) { overlay(properties, toAddTo); return new Resource(toAddTo, name); @@ -2812,15 +2879,9 @@ public class Configuration implements Iterable>, } catch (IOException e) { LOG.fatal("error parsing conf " + name, e); throw new RuntimeException(e); - } catch (DOMException e) { + } catch (XMLStreamException e) { LOG.fatal("error parsing conf " + name, e); throw new RuntimeException(e); - } catch (SAXException e) { - LOG.fatal("error parsing conf " + name, e); - throw new RuntimeException(e); - } catch (ParserConfigurationException e) { - LOG.fatal("error parsing conf " + name , e); - throw new RuntimeException(e); } } diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/conf/TestConfiguration.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/conf/TestConfiguration.java index 1f977865e22..6f4c26cf865 100644 --- a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/conf/TestConfiguration.java +++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/conf/TestConfiguration.java @@ -99,10 +99,22 @@ public class TestConfiguration extends TestCase { out.close(); } - private void addInclude(String filename) throws IOException{ - out.write("\n "); + private void startInclude(String filename) throws IOException { + out.write("\n "); } - + + private void endInclude() throws IOException{ + out.write("\n "); + } + + private void startFallback() throws IOException { + out.write("\n "); + } + + private void endFallback() throws IOException { + out.write("\n "); + } + public void testInputStreamResource() throws Exception { StringWriter writer = new StringWriter(); out = new BufferedWriter(writer); @@ -507,7 +519,8 @@ public class TestConfiguration extends TestCase { out=new BufferedWriter(new FileWriter(CONFIG)); startConfig(); - addInclude(CONFIG2); + startInclude(CONFIG2); + endInclude(); appendProperty("e","f"); appendProperty("g","h"); endConfig(); @@ -522,6 +535,44 @@ public class TestConfiguration extends TestCase { tearDown(); } + public void testIncludesWithFallback() throws Exception { + tearDown(); + out=new BufferedWriter(new FileWriter(CONFIG2)); + startConfig(); + appendProperty("a","b"); + appendProperty("c","d"); + endConfig(); + + out=new BufferedWriter(new FileWriter(CONFIG)); + startConfig(); + startInclude(CONFIG2); + startFallback(); + appendProperty("a", "b.fallback"); + appendProperty("c", "d.fallback", true); + endFallback(); + endInclude(); + appendProperty("e","f"); + appendProperty("g","h"); + startInclude("MissingConfig.xml"); + startFallback(); + appendProperty("i", "j.fallback"); + appendProperty("k", "l.fallback", true); + endFallback(); + endInclude(); + endConfig(); + + // verify that the includes file contains all properties + Path fileResource = new Path(CONFIG); + conf.addResource(fileResource); + assertEquals("b", conf.get("a")); + assertEquals("d", conf.get("c")); + assertEquals("f", conf.get("e")); + assertEquals("h", conf.get("g")); + assertEquals("j.fallback", conf.get("i")); + assertEquals("l.fallback", conf.get("k")); + tearDown(); + } + public void testRelativeIncludes() throws Exception { tearDown(); String relConfig = new File("./tmp/test-config.xml").getAbsolutePath(); @@ -536,7 +587,8 @@ public class TestConfiguration extends TestCase { out = new BufferedWriter(new FileWriter(relConfig)); startConfig(); // Add the relative path instead of the absolute one. - addInclude(new File(relConfig2).getName()); + startInclude(new File(relConfig2).getName()); + endInclude(); appendProperty("c", "d"); endConfig(); diff --git a/hadoop-project/pom.xml b/hadoop-project/pom.xml index 68e8b5c296b..e55308fed19 100644 --- a/hadoop-project/pom.xml +++ b/hadoop-project/pom.xml @@ -869,6 +869,16 @@ core 3.1.1 + + org.codehaus.woodstox + stax2-api + 3.1.4 + + + com.fasterxml + aalto-xml + 1.0.0 + org.codehaus.jackson jackson-mapper-asl From 15e3873dc3d46016344887570e5d4aa8d20ca264 Mon Sep 17 00:00:00 2001 From: Daniel Templeton Date: Wed, 29 Mar 2017 10:56:36 -0700 Subject: [PATCH 155/188] HDFS-11571. Typo in DataStorage exception message (Contributed by Anna Budai via Daniel Templeton) --- .../org/apache/hadoop/hdfs/server/datanode/DataStorage.java | 2 +- .../org/apache/hadoop/hdfs/server/datanode/TestDataStorage.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/DataStorage.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/DataStorage.java index 57ef1bcec99..835643bd9e7 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/DataStorage.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/DataStorage.java @@ -543,7 +543,7 @@ public class DataStorage extends Storage { void recoverTransitionRead(DataNode datanode, NamespaceInfo nsInfo, Collection dataDirs, StartupOption startOpt) throws IOException { if (addStorageLocations(datanode, nsInfo, dataDirs, startOpt).isEmpty()) { - throw new IOException("All specified directories are failed to load."); + throw new IOException("All specified directories have failed to load."); } } diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/datanode/TestDataStorage.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/datanode/TestDataStorage.java index c56a01ca58e..6c494519672 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/datanode/TestDataStorage.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/datanode/TestDataStorage.java @@ -200,7 +200,7 @@ public class TestDataStorage { fail("An IOException should throw: all StorageLocations are NON_EXISTENT"); } catch (IOException e) { GenericTestUtils.assertExceptionContains( - "All specified directories are failed to load.", e); + "All specified directories have failed to load.", e); } assertEquals(0, storage.getNumStorageDirs()); } From 13c766b62c48ae07849f7f1762ef6883239aef54 Mon Sep 17 00:00:00 2001 From: Mingliang Liu Date: Mon, 27 Mar 2017 16:37:39 -0700 Subject: [PATCH 156/188] HADOOP-14247. FileContextMainOperationsBaseTest should clean up test root path. Contributed by Mingliang Liu --- .../fs/FileContextMainOperationsBaseTest.java | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/FileContextMainOperationsBaseTest.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/FileContextMainOperationsBaseTest.java index 2b3ab2a0d98..ece96f855ab 100644 --- a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/FileContextMainOperationsBaseTest.java +++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/FileContextMainOperationsBaseTest.java @@ -36,6 +36,8 @@ import org.junit.Assert; import static org.junit.Assert.*; import org.junit.Before; import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import static org.apache.hadoop.fs.FileContextTestHelper.*; import static org.apache.hadoop.fs.CreateFlag.*; @@ -60,6 +62,9 @@ import static org.apache.hadoop.fs.CreateFlag.*; *

    */ public abstract class FileContextMainOperationsBaseTest { + + protected static final Logger LOG = + LoggerFactory.getLogger(FileContextMainOperationsBaseTest.class); private static String TEST_DIR_AAA2 = "test/hadoop2/aaa"; private static String TEST_DIR_AAA = "test/hadoop/aaa"; @@ -111,9 +116,19 @@ public abstract class FileContextMainOperationsBaseTest { @After public void tearDown() throws Exception { if (fc != null) { - boolean del = fc.delete(new Path(fileContextTestHelper.getAbsoluteTestRootPath(fc), new Path("test")), true); - assertTrue(del); - fc.delete(localFsRootPath, true); + final Path testRoot = fileContextTestHelper.getAbsoluteTestRootPath(fc); + LOG.info("Deleting test root path {}", testRoot); + try { + fc.delete(testRoot, true); + } catch (Exception e) { + LOG.error("Error when deleting test root path " + testRoot, e); + } + + try { + fc.delete(localFsRootPath, true); + } catch (Exception e) { + LOG.error("Error when deleting localFsRootPath " + localFsRootPath, e); + } } } From 640ba1d23fe8b8105bae6d342ddc1c839302f8e5 Mon Sep 17 00:00:00 2001 From: Daniel Templeton Date: Wed, 29 Mar 2017 12:36:15 -0700 Subject: [PATCH 157/188] YARN-5685. RM configuration allows all failover methods to disabled when automatic failover is enabled --- .../org/apache/hadoop/yarn/conf/HAUtil.java | 30 +++++- .../hadoop/yarn/conf/YarnConfiguration.java | 16 +++- .../apache/hadoop/yarn/conf/TestHAUtil.java | 86 ++++++++++------- .../resourcemanager/TestRMAdminService.java | 95 +++++++++++++++++++ 4 files changed, 191 insertions(+), 36 deletions(-) diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/HAUtil.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/HAUtil.java index e4948e74b05..133b377c0b5 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/HAUtil.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/HAUtil.java @@ -34,6 +34,7 @@ import java.util.Collection; public class HAUtil { private static Log LOG = LogFactory.getLog(HAUtil.class); + @VisibleForTesting public static final String BAD_CONFIG_MESSAGE_PREFIX = "Invalid configuration! "; @@ -79,6 +80,7 @@ public class HAUtil { throws YarnRuntimeException { verifyAndSetRMHAIdsList(conf); verifyAndSetCurrentRMHAId(conf); + verifyLeaderElection(conf); verifyAndSetAllServiceAddresses(conf); } @@ -117,7 +119,7 @@ public class HAUtil { msg.append("Can not find valid RM_HA_ID. None of "); for (String id : conf .getTrimmedStringCollection(YarnConfiguration.RM_HA_IDS)) { - msg.append(addSuffix(YarnConfiguration.RM_ADDRESS, id) + " "); + msg.append(addSuffix(YarnConfiguration.RM_ADDRESS, id)).append(" "); } msg.append(" are matching" + " the local address OR " + YarnConfiguration.RM_HA_ID + " is not" + @@ -133,6 +135,32 @@ public class HAUtil { conf.set(YarnConfiguration.RM_HA_ID, rmId); } + /** + * This method validates that some leader election service is enabled. YARN + * allows leadership election to be disabled in the configuration, which + * breaks automatic failover. If leadership election is disabled, this + * method will throw an exception via + * {@link #throwBadConfigurationException(java.lang.String)}. + * + * @param conf the {@link Configuration} to validate + */ + private static void verifyLeaderElection(Configuration conf) { + if (isAutomaticFailoverEnabled(conf) && + !conf.getBoolean(YarnConfiguration.CURATOR_LEADER_ELECTOR, + YarnConfiguration.DEFAULT_CURATOR_LEADER_ELECTOR_ENABLED) && + !isAutomaticFailoverEmbedded(conf)) { + throwBadConfigurationException(NO_LEADER_ELECTION_MESSAGE); + } + } + + @VisibleForTesting + static final String NO_LEADER_ELECTION_MESSAGE = + "The yarn.resourcemanager.ha.automatic-failover.embedded " + + "and yarn.resourcemanager.ha.curator-leader-elector.enabled " + + "properties are both false. One of these two properties must " + + "be true when yarn.resourcemanager.ha.automatic-failover.enabled " + + "is true"; + private static void verifyAndSetConfValue(String prefix, Configuration conf) { String confKey = null; String confValue = null; diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java index b36685533fd..f52e487524d 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java @@ -627,8 +627,22 @@ public class YarnConfiguration extends Configuration { AUTO_FAILOVER_PREFIX + "enabled"; public static final boolean DEFAULT_AUTO_FAILOVER_ENABLED = true; + /** + * This property controls whether {@link ActiveStandbyElector} leader + * election should be used when {@link #CURATOR_LEADER_ELECTOR} is + * {@code false}. + * + * @deprecated This property should never be set to {@code false}. + */ + @Deprecated public static final String AUTO_FAILOVER_EMBEDDED = AUTO_FAILOVER_PREFIX + "embedded"; + /** + * The default value for {@link #AUTO_FAILOVER_EMBEDDED}. + * + * @deprecated The {@link #AUTO_FAILOVER_EMBEDDED} property is deprecated. + */ + @Deprecated public static final boolean DEFAULT_AUTO_FAILOVER_EMBEDDED = true; public static final String AUTO_FAILOVER_ZK_BASE_PATH = @@ -667,7 +681,7 @@ public class YarnConfiguration extends Configuration { /** - * Whether to use curator-based elector for leader election. + * Whether to use the Curator-based elector for leader election. * * @deprecated Eventually, we want to default to the curator-based * implementation and remove the {@link ActiveStandbyElector} based diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/test/java/org/apache/hadoop/yarn/conf/TestHAUtil.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/test/java/org/apache/hadoop/yarn/conf/TestHAUtil.java index 6ced5f26326..fc2c1d0d335 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/test/java/org/apache/hadoop/yarn/conf/TestHAUtil.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/test/java/org/apache/hadoop/yarn/conf/TestHAUtil.java @@ -85,44 +85,47 @@ public class TestHAUtil { @Test public void testVerifyAndSetConfiguration() throws Exception { + Configuration myConf = new Configuration(conf); + try { - HAUtil.verifyAndSetConfiguration(conf); + HAUtil.verifyAndSetConfiguration(myConf); } catch (YarnRuntimeException e) { fail("Should not throw any exceptions."); } assertEquals("Should be saved as Trimmed collection", - StringUtils.getStringCollection(RM_NODE_IDS), HAUtil.getRMHAIds(conf)); + StringUtils.getStringCollection(RM_NODE_IDS), + HAUtil.getRMHAIds(myConf)); assertEquals("Should be saved as Trimmed string", - RM1_NODE_ID, HAUtil.getRMHAId(conf)); - for (String confKey : YarnConfiguration.getServiceAddressConfKeys(conf)) { + RM1_NODE_ID, HAUtil.getRMHAId(myConf)); + for (String confKey : YarnConfiguration.getServiceAddressConfKeys(myConf)) { assertEquals("RPC address not set for " + confKey, - RM1_ADDRESS, conf.get(confKey)); + RM1_ADDRESS, myConf.get(confKey)); } - conf.clear(); - conf.set(YarnConfiguration.RM_HA_IDS, RM1_NODE_ID); + myConf = new Configuration(conf); + myConf.set(YarnConfiguration.RM_HA_IDS, RM1_NODE_ID); try { - HAUtil.verifyAndSetConfiguration(conf); + HAUtil.verifyAndSetConfiguration(myConf); } catch (YarnRuntimeException e) { assertEquals("YarnRuntimeException by verifyAndSetRMHAIds()", HAUtil.BAD_CONFIG_MESSAGE_PREFIX + HAUtil.getInvalidValueMessage(YarnConfiguration.RM_HA_IDS, - conf.get(YarnConfiguration.RM_HA_IDS) + + myConf.get(YarnConfiguration.RM_HA_IDS) + "\nHA mode requires atleast two RMs"), e.getMessage()); } - conf.clear(); + myConf = new Configuration(conf); // simulate the case YarnConfiguration.RM_HA_ID is not set - conf.set(YarnConfiguration.RM_HA_IDS, RM1_NODE_ID + "," + myConf.set(YarnConfiguration.RM_HA_IDS, RM1_NODE_ID + "," + RM2_NODE_ID); - for (String confKey : YarnConfiguration.getServiceAddressConfKeys(conf)) { - conf.set(HAUtil.addSuffix(confKey, RM1_NODE_ID), RM1_ADDRESS); - conf.set(HAUtil.addSuffix(confKey, RM2_NODE_ID), RM2_ADDRESS); + for (String confKey : YarnConfiguration.getServiceAddressConfKeys(myConf)) { + myConf.set(HAUtil.addSuffix(confKey, RM1_NODE_ID), RM1_ADDRESS); + myConf.set(HAUtil.addSuffix(confKey, RM2_NODE_ID), RM2_ADDRESS); } try { - HAUtil.verifyAndSetConfiguration(conf); + HAUtil.verifyAndSetConfiguration(myConf); } catch (YarnRuntimeException e) { assertEquals("YarnRuntimeException by getRMId()", HAUtil.BAD_CONFIG_MESSAGE_PREFIX + @@ -130,16 +133,16 @@ public class TestHAUtil { e.getMessage()); } - conf.clear(); - conf.set(YarnConfiguration.RM_HA_ID, RM_INVALID_NODE_ID); - conf.set(YarnConfiguration.RM_HA_IDS, RM_INVALID_NODE_ID + "," + myConf = new Configuration(conf); + myConf.set(YarnConfiguration.RM_HA_ID, RM_INVALID_NODE_ID); + myConf.set(YarnConfiguration.RM_HA_IDS, RM_INVALID_NODE_ID + "," + RM1_NODE_ID); - for (String confKey : YarnConfiguration.getServiceAddressConfKeys(conf)) { + for (String confKey : YarnConfiguration.getServiceAddressConfKeys(myConf)) { // simulate xml with invalid node id - conf.set(confKey + RM_INVALID_NODE_ID, RM_INVALID_NODE_ID); + myConf.set(confKey + RM_INVALID_NODE_ID, RM_INVALID_NODE_ID); } try { - HAUtil.verifyAndSetConfiguration(conf); + HAUtil.verifyAndSetConfiguration(myConf); } catch (YarnRuntimeException e) { assertEquals("YarnRuntimeException by addSuffix()", HAUtil.BAD_CONFIG_MESSAGE_PREFIX + @@ -148,12 +151,12 @@ public class TestHAUtil { e.getMessage()); } - conf.clear(); + myConf = new Configuration(); // simulate the case HAUtil.RM_RPC_ADDRESS_CONF_KEYS are not set - conf.set(YarnConfiguration.RM_HA_ID, RM1_NODE_ID); - conf.set(YarnConfiguration.RM_HA_IDS, RM1_NODE_ID + "," + RM2_NODE_ID); + myConf.set(YarnConfiguration.RM_HA_ID, RM1_NODE_ID); + myConf.set(YarnConfiguration.RM_HA_IDS, RM1_NODE_ID + "," + RM2_NODE_ID); try { - HAUtil.verifyAndSetConfiguration(conf); + HAUtil.verifyAndSetConfiguration(myConf); fail("Should throw YarnRuntimeException. by Configuration#set()"); } catch (YarnRuntimeException e) { String confKey = @@ -166,21 +169,36 @@ public class TestHAUtil { // simulate the case YarnConfiguration.RM_HA_IDS doesn't contain // the value of YarnConfiguration.RM_HA_ID - conf.clear(); - conf.set(YarnConfiguration.RM_HA_IDS, RM2_NODE_ID + "," + RM3_NODE_ID); - conf.set(YarnConfiguration.RM_HA_ID, RM1_NODE_ID_UNTRIMMED); - for (String confKey : YarnConfiguration.getServiceAddressConfKeys(conf)) { - conf.set(HAUtil.addSuffix(confKey, RM1_NODE_ID), RM1_ADDRESS_UNTRIMMED); - conf.set(HAUtil.addSuffix(confKey, RM2_NODE_ID), RM2_ADDRESS); - conf.set(HAUtil.addSuffix(confKey, RM3_NODE_ID), RM3_ADDRESS); + myConf = new Configuration(conf); + myConf.set(YarnConfiguration.RM_HA_IDS, RM2_NODE_ID + "," + RM3_NODE_ID); + myConf.set(YarnConfiguration.RM_HA_ID, RM1_NODE_ID_UNTRIMMED); + for (String confKey : YarnConfiguration.getServiceAddressConfKeys(myConf)) { + myConf.set(HAUtil.addSuffix(confKey, RM1_NODE_ID), RM1_ADDRESS_UNTRIMMED); + myConf.set(HAUtil.addSuffix(confKey, RM2_NODE_ID), RM2_ADDRESS); + myConf.set(HAUtil.addSuffix(confKey, RM3_NODE_ID), RM3_ADDRESS); } try { - HAUtil.verifyAndSetConfiguration(conf); + HAUtil.verifyAndSetConfiguration(myConf); } catch (YarnRuntimeException e) { assertEquals("YarnRuntimeException by getRMId()'s validation", HAUtil.BAD_CONFIG_MESSAGE_PREFIX + HAUtil.getRMHAIdNeedToBeIncludedMessage("[rm2, rm3]", RM1_NODE_ID), - e.getMessage()); + e.getMessage()); + } + + // simulate the case that no leader election is enabled + myConf = new Configuration(conf); + myConf.setBoolean(YarnConfiguration.RM_HA_ENABLED, true); + myConf.setBoolean(YarnConfiguration.AUTO_FAILOVER_ENABLED, true); + myConf.setBoolean(YarnConfiguration.AUTO_FAILOVER_EMBEDDED, false); + myConf.setBoolean(YarnConfiguration.CURATOR_LEADER_ELECTOR, false); + + try { + HAUtil.verifyAndSetConfiguration(myConf); + } catch (YarnRuntimeException e) { + assertEquals("YarnRuntimeException by getRMId()'s validation", + HAUtil.BAD_CONFIG_MESSAGE_PREFIX + HAUtil.NO_LEADER_ELECTION_MESSAGE, + e.getMessage()); } } diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestRMAdminService.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestRMAdminService.java index 9ae28c2bf83..8d00ae071e2 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestRMAdminService.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestRMAdminService.java @@ -78,6 +78,8 @@ import org.junit.Test; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import org.apache.hadoop.yarn.exceptions.YarnRuntimeException; +import static org.junit.Assert.assertTrue; public class TestRMAdminService { @@ -841,6 +843,78 @@ public class TestRMAdminService { } } + /** + * Test that a configuration with no leader election configured fails. + */ + @Test + public void testHAConfWithoutLeaderElection() { + Configuration conf = new Configuration(configuration); + + conf.setBoolean(YarnConfiguration.RM_HA_ENABLED, true); + conf.setBoolean(YarnConfiguration.AUTO_FAILOVER_ENABLED, true); + conf.setBoolean(YarnConfiguration.AUTO_FAILOVER_EMBEDDED, false); + conf.setBoolean(YarnConfiguration.CURATOR_LEADER_ELECTOR, false); + conf.set(YarnConfiguration.RM_HA_IDS, "rm1,rm2"); + + int base = 100; + + for (String confKey : + YarnConfiguration.getServiceAddressConfKeys(configuration)) { + conf.set(HAUtil.addSuffix(confKey, "rm1"), "0.0.0.0:" + + (base + 20)); + conf.set(HAUtil.addSuffix(confKey, "rm2"), "0.0.0.0:" + + (base + 40)); + base = base * 2; + } + + conf.set(YarnConfiguration.RM_HA_ID, "rm1"); + + checkBadConfiguration(conf); + } + + /** + * Test that a configuration with a single RM fails. + */ + @Test + public void testHAConfWithSingleRMID() { + Configuration conf = new Configuration(configuration); + + conf.setBoolean(YarnConfiguration.RM_HA_ENABLED, true); + conf.setBoolean(YarnConfiguration.AUTO_FAILOVER_ENABLED, true); + conf.setBoolean(YarnConfiguration.AUTO_FAILOVER_EMBEDDED, true); + conf.set(YarnConfiguration.RM_HA_IDS, "rm1"); + + int base = 100; + + for (String confKey : + YarnConfiguration.getServiceAddressConfKeys(configuration)) { + conf.set(HAUtil.addSuffix(confKey, "rm1"), "0.0.0.0:" + + (base + 20)); + base = base * 2; + } + + conf.set(YarnConfiguration.RM_HA_ID, "rm1"); + + checkBadConfiguration(conf); + } + + /** + * Test that a configuration with no service information fails. + */ + @Test + public void testHAConfWithoutServiceInfo() { + Configuration conf = new Configuration(configuration); + + conf.setBoolean(YarnConfiguration.RM_HA_ENABLED, true); + conf.setBoolean(YarnConfiguration.AUTO_FAILOVER_ENABLED, true); + conf.setBoolean(YarnConfiguration.AUTO_FAILOVER_EMBEDDED, true); + conf.set(YarnConfiguration.RM_HA_IDS, "rm1,rm2"); + + conf.set(YarnConfiguration.RM_HA_ID, "rm1"); + + checkBadConfiguration(conf); + } + @Test public void testRMStartsWithoutConfigurationFilesProvided() { // enable FileSystemBasedConfigurationProvider without uploading @@ -1368,4 +1442,25 @@ public class TestRMAdminService { base = base * 2; } } + + /** + * This method initializes an RM with the given configuration and expects it + * to fail with a configuration error. + * + * @param conf the {@link Configuration} to use + */ + private void checkBadConfiguration(Configuration conf) { + MockRM rm1 = null; + + conf.set(YarnConfiguration.RM_HA_ID, "rm1"); + + try { + rm1 = new MockRM(conf); + rm1.init(conf); + fail("The RM allowed an invalid configuration"); + } catch (YarnRuntimeException e) { + assertTrue("The RM initialization threw an unexpected exception", + e.getMessage().startsWith(HAUtil.BAD_CONFIG_MESSAGE_PREFIX)); + } + } } From 4966a6e26e45d7dc36e0b270066ff7c87bcd00cc Mon Sep 17 00:00:00 2001 From: Andrew Wang Date: Wed, 29 Mar 2017 14:37:21 -0700 Subject: [PATCH 158/188] HADOOP-14223. Extend FileStatus#toString() to include details like Erasure Coding and Encryption. Contributed by Manoj Govindassamy. --- .../java/org/apache/hadoop/fs/FileStatus.java | 12 ++++++++++ .../site/markdown/filesystem/filesystem.md | 19 ++++++--------- .../org/apache/hadoop/fs/TestFileStatus.java | 10 +++++--- .../fs/viewfs/TestViewfsFileStatus.java | 3 +++ .../apache/hadoop/hdfs/TestFileStatus.java | 3 +++ .../hdfs/TestFileStatusWithECPolicy.java | 7 +++++- .../hdfs/server/namenode/FSAclBaseTest.java | 23 +++++++++++++++++++ .../apache/hadoop/hdfs/web/TestWebHDFS.java | 12 ++++++++++ .../hadoop/fs/s3a/TestS3AGetFileStatus.java | 6 +++++ .../hadoop/fs/adl/TestGetFileStatus.java | 7 ++++++ 10 files changed, 86 insertions(+), 16 deletions(-) diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/FileStatus.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/FileStatus.java index 26d39627f50..f5111ef6f0a 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/FileStatus.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/FileStatus.java @@ -207,6 +207,15 @@ public class FileStatus implements Writable, Comparable, return permission; } + /** + * Tell whether the underlying file or directory has ACLs set. + * + * @return true if the underlying file or directory has ACLs set. + */ + public boolean hasAcl() { + return permission.getAclBit(); + } + /** * Tell whether the underlying file or directory is encrypted or not. * @@ -399,6 +408,9 @@ public class FileStatus implements Writable, Comparable, if(isSymlink()) { sb.append("; symlink=" + symlink); } + sb.append("; hasAcl=" + hasAcl()); + sb.append("; isEncrypted=" + isEncrypted()); + sb.append("; isErasureCoded=" + isErasureCoded()); sb.append("}"); return sb.toString(); } diff --git a/hadoop-common-project/hadoop-common/src/site/markdown/filesystem/filesystem.md b/hadoop-common-project/hadoop-common/src/site/markdown/filesystem/filesystem.md index 97bc7d19dfd..b464941537b 100644 --- a/hadoop-common-project/hadoop-common/src/site/markdown/filesystem/filesystem.md +++ b/hadoop-common-project/hadoop-common/src/site/markdown/filesystem/filesystem.md @@ -86,20 +86,15 @@ Get the status of a path stat.length = 0 stat.isdir = False stat.symlink = FS.Symlinks[p] - if inEncryptionZone(FS, p) : - stat.isEncrypted = True - else - stat.isEncrypted = False - if isErasureCoded(FS, p) : - stat.isErasureCoded = True - else - stat.isErasureCoded = False + stat.hasAcl = hasACL(FS, p) + stat.isEncrypted = inEncryptionZone(FS, p) + stat.isErasureCoded = isErasureCoded(FS, p) The returned `FileStatus` status of the path additionally carries details on -Encryption and Erasure Coding information. `getFileStatus(Path p).isEncrypted()` -can be queried to find if the path is Encrypted. -Likewise, `getFileStatus(Path p).isErasureCoded()` will tell if the path is -Erasure Coded or not. +ACL, encryption and erasure coding information. `getFileStatus(Path p).hasAcl()` +can be queried to find if the path has an ACL. `getFileStatus(Path p).isEncrypted()` +can be queried to find if the path is encrypted. `getFileStatus(Path p).isErasureCoded()` +will tell if the path is erasure coded or not. ### `Path getHomeDirectory()` diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/TestFileStatus.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/TestFileStatus.java index 35f2bad5657..d29b1a40711 100644 --- a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/TestFileStatus.java +++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/TestFileStatus.java @@ -295,11 +295,15 @@ public class TestFileStatus { expected.append("permission=").append(fileStatus.getPermission()).append("; "); if(fileStatus.isSymlink()) { expected.append("isSymlink=").append(true).append("; "); - expected.append("symlink=").append(fileStatus.getSymlink()).append("}"); + expected.append("symlink=").append(fileStatus.getSymlink()).append("; "); } else { - expected.append("isSymlink=").append(false).append("}"); + expected.append("isSymlink=").append(false).append("; "); } - + expected.append("hasAcl=").append(fileStatus.hasAcl()).append("; "); + expected.append("isEncrypted=").append( + fileStatus.isEncrypted()).append("; "); + expected.append("isErasureCoded=").append( + fileStatus.isErasureCoded()).append("}"); assertEquals(expected.toString(), fileStatus.toString()); } } diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/viewfs/TestViewfsFileStatus.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/viewfs/TestViewfsFileStatus.java index 449e2e33621..0c31c8ed6a9 100644 --- a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/viewfs/TestViewfsFileStatus.java +++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/viewfs/TestViewfsFileStatus.java @@ -75,6 +75,9 @@ public class TestViewfsFileStatus { FileStatus stat = vfs.getFileStatus(path); assertEquals(content.length, stat.getLen()); ContractTestUtils.assertNotErasureCoded(vfs, path); + assertTrue(path + " should have erasure coding unset in " + + "FileStatus#toString(): " + stat, + stat.toString().contains("isErasureCoded=false")); // check serialization/deserialization DataOutputBuffer dob = new DataOutputBuffer(); diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestFileStatus.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestFileStatus.java index 7631e7d70cc..c74bb632a74 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestFileStatus.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestFileStatus.java @@ -139,6 +139,9 @@ public class TestFileStatus { assertEquals(file1.makeQualified(fs.getUri(), fs.getWorkingDirectory()).toString(), status.getPath().toString()); + assertTrue(file1 + " should have erasure coding unset in " + + "FileStatus#toString(): " + status, + status.toString().contains("isErasureCoded=false")); } /** Test the FileStatus obtained calling listStatus on a file */ diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestFileStatusWithECPolicy.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestFileStatusWithECPolicy.java index 0e270ff75c7..e04f9573256 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestFileStatusWithECPolicy.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestFileStatusWithECPolicy.java @@ -23,6 +23,7 @@ import static org.junit.Assert.assertTrue; import java.io.IOException; +import org.apache.hadoop.fs.FileStatus; import org.apache.hadoop.fs.Path; import org.apache.hadoop.fs.contract.ContractTestUtils; import org.apache.hadoop.fs.permission.FsPermission; @@ -85,7 +86,7 @@ public class TestFileStatusWithECPolicy { assertNotNull(ecPolicy2); assertTrue(ecPolicy1.equals(ecPolicy2)); - // test file doesn't have an EC policy + // test file with EC policy fs.create(file).close(); final ErasureCodingPolicy ecPolicy3 = fs.getClient().getFileInfo(file.toUri().getPath()) @@ -93,5 +94,9 @@ public class TestFileStatusWithECPolicy { assertNotNull(ecPolicy3); assertTrue(ecPolicy1.equals(ecPolicy3)); ContractTestUtils.assertErasureCoded(fs, file); + FileStatus status = fs.getFileStatus(file); + assertTrue(file + " should have erasure coding set in " + + "FileStatus#toString(): " + status, + status.toString().contains("isErasureCoded=true")); } } diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/FSAclBaseTest.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/FSAclBaseTest.java index 81270e6f2bd..60b0ab168d2 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/FSAclBaseTest.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/FSAclBaseTest.java @@ -50,6 +50,7 @@ import org.apache.hadoop.security.AccessControlException; import org.apache.hadoop.security.UserGroupInformation; import org.junit.After; import org.junit.AfterClass; +import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -120,10 +121,16 @@ public abstract class FSAclBaseTest { aclEntry(ACCESS, OTHER, NONE), aclEntry(DEFAULT, USER, "foo", ALL)); fs.setAcl(path, aclSpec); + Assert.assertTrue(path + " should have ACLs in FileStatus!", + fs.getFileStatus(path).hasAcl()); + aclSpec = Lists.newArrayList( aclEntry(ACCESS, USER, "foo", READ_EXECUTE), aclEntry(DEFAULT, USER, "foo", READ_EXECUTE)); fs.modifyAclEntries(path, aclSpec); + Assert.assertTrue(path + " should have ACLs in FileStatus!", + fs.getFileStatus(path).hasAcl()); + AclStatus s = fs.getAclStatus(path); AclEntry[] returned = s.getEntries().toArray(new AclEntry[0]); assertArrayEquals(new AclEntry[] { @@ -561,8 +568,18 @@ public abstract class FSAclBaseTest { aclEntry(ACCESS, GROUP, READ_EXECUTE), aclEntry(ACCESS, OTHER, NONE), aclEntry(DEFAULT, USER, "foo", ALL)); + fs.setAcl(path, aclSpec); + Assert.assertTrue(path + " should have ACLs in FileStatus!", + fs.getFileStatus(path).hasAcl()); + Assert.assertTrue(path + " should have ACLs in FileStatus#toString()!", + fs.getFileStatus(path).toString().contains("hasAcl=true")); fs.removeAcl(path); + Assert.assertFalse(path + " should not have ACLs in FileStatus!", + fs.getFileStatus(path).hasAcl()); + Assert.assertTrue(path + " should not have ACLs in FileStatus#toString()!", + fs.getFileStatus(path).toString().contains("hasAcl=false")); + AclStatus s = fs.getAclStatus(path); AclEntry[] returned = s.getEntries().toArray(new AclEntry[0]); assertArrayEquals(new AclEntry[] { }, returned); @@ -968,8 +985,14 @@ public abstract class FSAclBaseTest { List aclSpec = Lists.newArrayList( aclEntry(DEFAULT, USER, "foo", ALL)); fs.setAcl(path, aclSpec); + Assert.assertTrue(path + " should have ACLs in FileStatus!", + fs.getFileStatus(path).hasAcl()); + Path dirPath = new Path(path, "dir1"); fs.mkdirs(dirPath); + Assert.assertTrue(dirPath + " should have ACLs in FileStatus!", + fs.getFileStatus(dirPath).hasAcl()); + AclStatus s = fs.getAclStatus(dirPath); AclEntry[] returned = s.getEntries().toArray(new AclEntry[0]); assertArrayEquals(new AclEntry[] { diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/web/TestWebHDFS.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/web/TestWebHDFS.java index 3c366c39dad..46a8a56f1c1 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/web/TestWebHDFS.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/web/TestWebHDFS.java @@ -547,24 +547,36 @@ public class TestWebHDFS { Assert.assertEquals(expectedECDirStatus.isErasureCoded(), actualECDirStatus.isErasureCoded()); ContractTestUtils.assertErasureCoded(dfs, ecDir); + assertTrue(ecDir+ " should have erasure coding set in " + + "FileStatus#toString(): " + actualECDirStatus, + actualECDirStatus.toString().contains("isErasureCoded=true")); FileStatus expectedECFileStatus = dfs.getFileStatus(ecFile); FileStatus actualECFileStatus = webHdfs.getFileStatus(ecFile); Assert.assertEquals(expectedECFileStatus.isErasureCoded(), actualECFileStatus.isErasureCoded()); ContractTestUtils.assertErasureCoded(dfs, ecFile); + assertTrue(ecFile+ " should have erasure coding set in " + + "FileStatus#toString(): " + actualECFileStatus, + actualECFileStatus.toString().contains("isErasureCoded=true")); FileStatus expectedNormalDirStatus = dfs.getFileStatus(normalDir); FileStatus actualNormalDirStatus = webHdfs.getFileStatus(normalDir); Assert.assertEquals(expectedNormalDirStatus.isErasureCoded(), actualNormalDirStatus.isErasureCoded()); ContractTestUtils.assertNotErasureCoded(dfs, normalDir); + assertTrue(normalDir + " should have erasure coding unset in " + + "FileStatus#toString(): " + actualNormalDirStatus, + actualNormalDirStatus.toString().contains("isErasureCoded=false")); FileStatus expectedNormalFileStatus = dfs.getFileStatus(normalFile); FileStatus actualNormalFileStatus = webHdfs.getFileStatus(normalDir); Assert.assertEquals(expectedNormalFileStatus.isErasureCoded(), actualNormalFileStatus.isErasureCoded()); ContractTestUtils.assertNotErasureCoded(dfs, normalFile); + assertTrue(normalFile + " should have erasure coding unset in " + + "FileStatus#toString(): " + actualNormalFileStatus, + actualNormalFileStatus.toString().contains("isErasureCoded=false")); } finally { if (cluster != null) { diff --git a/hadoop-tools/hadoop-aws/src/test/java/org/apache/hadoop/fs/s3a/TestS3AGetFileStatus.java b/hadoop-tools/hadoop-aws/src/test/java/org/apache/hadoop/fs/s3a/TestS3AGetFileStatus.java index deb3963b2d2..58e4d3074bc 100644 --- a/hadoop-tools/hadoop-aws/src/test/java/org/apache/hadoop/fs/s3a/TestS3AGetFileStatus.java +++ b/hadoop-tools/hadoop-aws/src/test/java/org/apache/hadoop/fs/s3a/TestS3AGetFileStatus.java @@ -62,6 +62,9 @@ public class TestS3AGetFileStatus extends AbstractS3AMockTest { assertEquals(meta.getContentLength(), stat.getLen()); assertEquals(meta.getLastModified().getTime(), stat.getModificationTime()); ContractTestUtils.assertNotErasureCoded(fs, path); + assertTrue(path + " should have erasure coding unset in " + + "FileStatus#toString(): " + stat, + stat.toString().contains("isErasureCoded=false")); } @Test @@ -101,6 +104,9 @@ public class TestS3AGetFileStatus extends AbstractS3AMockTest { assertEquals(fs.makeQualified(path), stat.getPath()); assertTrue(stat.isDirectory()); ContractTestUtils.assertNotErasureCoded(fs, path); + assertTrue(path + " should have erasure coding unset in " + + "FileStatus#toString(): " + stat, + stat.toString().contains("isErasureCoded=false")); } @Test diff --git a/hadoop-tools/hadoop-azure-datalake/src/test/java/org/apache/hadoop/fs/adl/TestGetFileStatus.java b/hadoop-tools/hadoop-azure-datalake/src/test/java/org/apache/hadoop/fs/adl/TestGetFileStatus.java index df39a40cebd..0ea4b868c1d 100644 --- a/hadoop-tools/hadoop-azure-datalake/src/test/java/org/apache/hadoop/fs/adl/TestGetFileStatus.java +++ b/hadoop-tools/hadoop-azure-datalake/src/test/java/org/apache/hadoop/fs/adl/TestGetFileStatus.java @@ -65,6 +65,9 @@ public class TestGetFileStatus extends AdlMockWebServer { Assert.assertEquals(new FsPermission("777"), fileStatus.getPermission()); Assert.assertEquals("NotSupportYet", fileStatus.getOwner()); Assert.assertEquals("NotSupportYet", fileStatus.getGroup()); + Assert.assertTrue(path + " should have Acl!", fileStatus.hasAcl()); + Assert.assertFalse(path + " should not be encrypted!", + fileStatus.isEncrypted()); Assert.assertFalse(path + " should not be erasure coded!", fileStatus.isErasureCoded()); } @@ -82,6 +85,8 @@ public class TestGetFileStatus extends AdlMockWebServer { LOG.debug("Time : " + (endTime - startTime)); Assert.assertTrue(fileStatus.isFile()); Assert.assertEquals(true, fileStatus.getPermission().getAclBit()); + Assert.assertEquals(fileStatus.hasAcl(), + fileStatus.getPermission().getAclBit()); // With ACLBIT set to false getMockServer().enqueue(new MockResponse().setResponseCode(200) @@ -93,5 +98,7 @@ public class TestGetFileStatus extends AdlMockWebServer { LOG.debug("Time : " + (endTime - startTime)); Assert.assertTrue(fileStatus.isFile()); Assert.assertEquals(false, fileStatus.getPermission().getAclBit()); + Assert.assertEquals(fileStatus.hasAcl(), + fileStatus.getPermission().getAclBit()); } } From 6a5516c2381f107d96b8326939514de3c6e53d3d Mon Sep 17 00:00:00 2001 From: Robert Kanter Date: Wed, 29 Mar 2017 16:18:13 -0700 Subject: [PATCH 159/188] YARN-5654. Not be able to run SLS with FairScheduler (yufeigu via rkanter) --- .../org/apache/hadoop/yarn/sls/SLSRunner.java | 22 +- .../yarn/sls/appmaster/AMSimulator.java | 24 +- .../scheduler/ResourceSchedulerWrapper.java | 969 ------------------ .../sls/scheduler/SLSCapacityScheduler.java | 685 ++----------- .../yarn/sls/scheduler/SLSFairScheduler.java | 339 ++++++ .../yarn/sls/scheduler/SchedulerMetrics.java | 533 +++++++++- .../yarn/sls/scheduler/SchedulerWrapper.java | 18 +- .../hadoop/yarn/sls/scheduler/Tracker.java | 46 + .../hadoop/yarn/sls/utils/SLSUtils.java | 1 + .../apache/hadoop/yarn/sls/web/SLSWebApp.java | 12 +- .../yarn/sls/appmaster/TestAMSimulator.java | 53 +- .../yarn/sls/nodemanager/TestNMSimulator.java | 32 +- 12 files changed, 1103 insertions(+), 1631 deletions(-) delete mode 100644 hadoop-tools/hadoop-sls/src/main/java/org/apache/hadoop/yarn/sls/scheduler/ResourceSchedulerWrapper.java create mode 100644 hadoop-tools/hadoop-sls/src/main/java/org/apache/hadoop/yarn/sls/scheduler/SLSFairScheduler.java create mode 100644 hadoop-tools/hadoop-sls/src/main/java/org/apache/hadoop/yarn/sls/scheduler/Tracker.java diff --git a/hadoop-tools/hadoop-sls/src/main/java/org/apache/hadoop/yarn/sls/SLSRunner.java b/hadoop-tools/hadoop-sls/src/main/java/org/apache/hadoop/yarn/sls/SLSRunner.java index 61b7f36ee9c..ba43816de19 100644 --- a/hadoop-tools/hadoop-sls/src/main/java/org/apache/hadoop/yarn/sls/SLSRunner.java +++ b/hadoop-tools/hadoop-sls/src/main/java/org/apache/hadoop/yarn/sls/SLSRunner.java @@ -59,16 +59,14 @@ import org.apache.hadoop.yarn.server.resourcemanager.ResourceManager; import org.apache.hadoop.yarn.server.resourcemanager.amlauncher.ApplicationMasterLauncher; import org.apache.hadoop.yarn.server.resourcemanager.rmnode.RMNode; import org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CapacityScheduler; +import org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FairScheduler; +import org.apache.hadoop.yarn.server.resourcemanager.scheduler.fifo.FifoScheduler; import org.apache.hadoop.yarn.server.utils.BuilderUtils; import org.apache.hadoop.yarn.sls.appmaster.AMSimulator; import org.apache.hadoop.yarn.sls.conf.SLSConfiguration; import org.apache.hadoop.yarn.sls.nodemanager.NMSimulator; import org.apache.hadoop.yarn.sls.resourcemanager.MockAMLauncher; -import org.apache.hadoop.yarn.sls.scheduler.ContainerSimulator; -import org.apache.hadoop.yarn.sls.scheduler.ResourceSchedulerWrapper; -import org.apache.hadoop.yarn.sls.scheduler.SLSCapacityScheduler; -import org.apache.hadoop.yarn.sls.scheduler.SchedulerWrapper; -import org.apache.hadoop.yarn.sls.scheduler.TaskRunner; +import org.apache.hadoop.yarn.sls.scheduler.*; import org.apache.hadoop.yarn.sls.utils.SLSUtils; import org.apache.hadoop.yarn.util.resource.Resources; import org.apache.log4j.Logger; @@ -152,9 +150,9 @@ public class SLSRunner { // start application masters startAM(); // set queue & tracked apps information - ((SchedulerWrapper) rm.getResourceScheduler()) + ((SchedulerWrapper) rm.getResourceScheduler()).getTracker() .setQueueSet(this.queueAppNumMap.keySet()); - ((SchedulerWrapper) rm.getResourceScheduler()) + ((SchedulerWrapper) rm.getResourceScheduler()).getTracker() .setTrackedAppSet(this.trackedApps); // print out simulation info printSimulationInfo(); @@ -164,7 +162,7 @@ public class SLSRunner { runner.start(); } - private void startRM() throws IOException, ClassNotFoundException { + private void startRM() throws Exception { Configuration rmConf = new YarnConfiguration(); String schedulerClass = rmConf.get(YarnConfiguration.RM_SCHEDULER); @@ -175,10 +173,12 @@ public class SLSRunner { if(Class.forName(schedulerClass) == CapacityScheduler.class) { rmConf.set(YarnConfiguration.RM_SCHEDULER, SLSCapacityScheduler.class.getName()); - } else { + } else if (Class.forName(schedulerClass) == FairScheduler.class) { rmConf.set(YarnConfiguration.RM_SCHEDULER, - ResourceSchedulerWrapper.class.getName()); - rmConf.set(SLSConfiguration.RM_SCHEDULER, schedulerClass); + SLSFairScheduler.class.getName()); + } else if (Class.forName(schedulerClass) == FifoScheduler.class){ + // TODO add support for FifoScheduler + throw new Exception("Fifo Scheduler is not supported yet."); } rmConf.set(SLSConfiguration.METRICS_OUTPUT_DIR, metricsOutputDir); diff --git a/hadoop-tools/hadoop-sls/src/main/java/org/apache/hadoop/yarn/sls/appmaster/AMSimulator.java b/hadoop-tools/hadoop-sls/src/main/java/org/apache/hadoop/yarn/sls/appmaster/AMSimulator.java index 0573bae0ecf..a62f2b60240 100644 --- a/hadoop-tools/hadoop-sls/src/main/java/org/apache/hadoop/yarn/sls/appmaster/AMSimulator.java +++ b/hadoop-tools/hadoop-sls/src/main/java/org/apache/hadoop/yarn/sls/appmaster/AMSimulator.java @@ -62,6 +62,7 @@ import org.apache.hadoop.yarn.factories.RecordFactory; import org.apache.hadoop.yarn.factory.providers.RecordFactoryProvider; import org.apache.hadoop.yarn.security.AMRMTokenIdentifier; import org.apache.hadoop.yarn.server.resourcemanager.ResourceManager; +import org.apache.hadoop.yarn.sls.scheduler.SchedulerMetrics; import org.apache.hadoop.yarn.util.Records; import org.apache.hadoop.yarn.util.resource.Resources; import org.apache.log4j.Logger; @@ -219,10 +220,12 @@ public abstract class AMSimulator extends TaskRunner.Task { simulateFinishTimeMS = System.currentTimeMillis() - SLSRunner.getRunner().getStartTimeMS(); // record job running information - ((SchedulerWrapper)rm.getResourceScheduler()) - .addAMRuntime(appId, - traceStartTimeMS, traceFinishTimeMS, - simulateStartTimeMS, simulateFinishTimeMS); + SchedulerMetrics schedulerMetrics = + ((SchedulerWrapper)rm.getResourceScheduler()).getSchedulerMetrics(); + if (schedulerMetrics != null) { + schedulerMetrics.addAMRuntime(appId, traceStartTimeMS, traceFinishTimeMS, + simulateStartTimeMS, simulateFinishTimeMS); + } } protected ResourceRequest createResourceRequest( @@ -330,13 +333,20 @@ public abstract class AMSimulator extends TaskRunner.Task { private void trackApp() { if (isTracked) { - ((SchedulerWrapper) rm.getResourceScheduler()) - .addTrackedApp(appId, oldAppId); + SchedulerMetrics schedulerMetrics = + ((SchedulerWrapper)rm.getResourceScheduler()).getSchedulerMetrics(); + if (schedulerMetrics != null) { + schedulerMetrics.addTrackedApp(appId, oldAppId); + } } } public void untrackApp() { if (isTracked) { - ((SchedulerWrapper) rm.getResourceScheduler()).removeTrackedApp(oldAppId); + SchedulerMetrics schedulerMetrics = + ((SchedulerWrapper)rm.getResourceScheduler()).getSchedulerMetrics(); + if (schedulerMetrics != null) { + schedulerMetrics.removeTrackedApp(oldAppId); + } } } diff --git a/hadoop-tools/hadoop-sls/src/main/java/org/apache/hadoop/yarn/sls/scheduler/ResourceSchedulerWrapper.java b/hadoop-tools/hadoop-sls/src/main/java/org/apache/hadoop/yarn/sls/scheduler/ResourceSchedulerWrapper.java deleted file mode 100644 index a4b8e642ba5..00000000000 --- a/hadoop-tools/hadoop-sls/src/main/java/org/apache/hadoop/yarn/sls/scheduler/ResourceSchedulerWrapper.java +++ /dev/null @@ -1,969 +0,0 @@ -/** - * 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.yarn.sls.scheduler; - -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; -import java.util.SortedMap; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - -import org.apache.hadoop.classification.InterfaceAudience.LimitedPrivate; -import org.apache.hadoop.classification.InterfaceAudience.Private; -import org.apache.hadoop.classification.InterfaceStability.Unstable; -import org.apache.hadoop.conf.Configurable; -import org.apache.hadoop.conf.Configuration; -import org.apache.hadoop.security.UserGroupInformation; -import org.apache.hadoop.util.ReflectionUtils; -import org.apache.hadoop.util.ShutdownHookManager; -import org.apache.hadoop.yarn.api.records.ApplicationAttemptId; -import org.apache.hadoop.yarn.api.records.ApplicationId; -import org.apache.hadoop.yarn.api.records.ApplicationResourceUsageReport; -import org.apache.hadoop.yarn.api.records.Container; -import org.apache.hadoop.yarn.api.records.ContainerExitStatus; -import org.apache.hadoop.yarn.api.records.ContainerId; -import org.apache.hadoop.yarn.api.records.ContainerStatus; -import org.apache.hadoop.yarn.api.records.NodeId; -import org.apache.hadoop.yarn.api.records.Priority; -import org.apache.hadoop.yarn.api.records.QueueACL; -import org.apache.hadoop.yarn.api.records.QueueInfo; -import org.apache.hadoop.yarn.api.records.QueueUserACLInfo; -import org.apache.hadoop.yarn.api.records.Resource; -import org.apache.hadoop.yarn.api.records.ResourceRequest; -import org.apache.hadoop.yarn.exceptions.YarnException; -import org.apache.hadoop.yarn.server.resourcemanager.RMContext; -import org.apache.hadoop.yarn.server.resourcemanager.recovery.RMStateStore; -import org.apache.hadoop.yarn.server.resourcemanager.rmcontainer.RMContainer; -import org.apache.hadoop.yarn.server.resourcemanager.rmcontainer.RMContainerEventType; -import org.apache.hadoop.yarn.server.resourcemanager.rmnode.UpdatedContainerInfo; -import org.apache.hadoop.yarn.server.resourcemanager.scheduler.AbstractYarnScheduler; -import org.apache.hadoop.yarn.server.resourcemanager.scheduler.Allocation; -import org.apache.hadoop.yarn.server.resourcemanager.scheduler.ContainerUpdates; -import org.apache.hadoop.yarn.server.resourcemanager.scheduler.QueueMetrics; -import org.apache.hadoop.yarn.server.resourcemanager.scheduler.ResourceScheduler; -import org.apache.hadoop.yarn.server.resourcemanager.scheduler.SchedulerAppReport; -import org.apache.hadoop.yarn.server.resourcemanager.scheduler.SchedulerApplication; -import org.apache.hadoop.yarn.server.resourcemanager.scheduler.SchedulerApplicationAttempt; -import org.apache.hadoop.yarn.server.resourcemanager.scheduler.SchedulerNode; -import org.apache.hadoop.yarn.server.resourcemanager.scheduler.SchedulerNodeReport; -import org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CapacityScheduler; -import org.apache.hadoop.yarn.server.resourcemanager.scheduler.event.AppAddedSchedulerEvent; -import org.apache.hadoop.yarn.server.resourcemanager.scheduler.event.AppAttemptRemovedSchedulerEvent; -import org.apache.hadoop.yarn.server.resourcemanager.scheduler.event.AppRemovedSchedulerEvent; -import org.apache.hadoop.yarn.server.resourcemanager.scheduler.event.NodeUpdateSchedulerEvent; -import org.apache.hadoop.yarn.server.resourcemanager.scheduler.event.SchedulerEvent; -import org.apache.hadoop.yarn.server.resourcemanager.scheduler.event.SchedulerEventType; -import org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FairScheduler; -import org.apache.hadoop.yarn.server.resourcemanager.scheduler.fifo.FifoScheduler; -import org.apache.hadoop.yarn.sls.SLSRunner; -import org.apache.hadoop.yarn.sls.conf.SLSConfiguration; -import org.apache.hadoop.yarn.sls.web.SLSWebApp; -import org.apache.hadoop.yarn.util.resource.ResourceCalculator; -import org.apache.hadoop.yarn.util.resource.Resources; -import org.apache.log4j.Logger; - -import com.codahale.metrics.Counter; -import com.codahale.metrics.CsvReporter; -import com.codahale.metrics.Gauge; -import com.codahale.metrics.Histogram; -import com.codahale.metrics.MetricRegistry; -import com.codahale.metrics.SlidingWindowReservoir; -import com.codahale.metrics.Timer; - -@Private -@Unstable -final public class ResourceSchedulerWrapper - extends AbstractYarnScheduler - implements SchedulerWrapper, ResourceScheduler, Configurable { - private static final String EOL = System.getProperty("line.separator"); - private static final int SAMPLING_SIZE = 60; - private ScheduledExecutorService pool; - // counters for scheduler allocate/handle operations - private Counter schedulerAllocateCounter; - private Counter schedulerHandleCounter; - private Map schedulerHandleCounterMap; - // Timers for scheduler allocate/handle operations - private Timer schedulerAllocateTimer; - private Timer schedulerHandleTimer; - private Map schedulerHandleTimerMap; - private List schedulerHistogramList; - private Map histogramTimerMap; - private Lock samplerLock; - private Lock queueLock; - - private Configuration conf; - private ResourceScheduler scheduler; - private Map appQueueMap = - new ConcurrentHashMap(); - private BufferedWriter jobRuntimeLogBW; - - // Priority of the ResourceSchedulerWrapper shutdown hook. - public static final int SHUTDOWN_HOOK_PRIORITY = 30; - - // web app - private SLSWebApp web; - - private Map preemptionContainerMap = - new ConcurrentHashMap(); - - // metrics - private MetricRegistry metrics; - private SchedulerMetrics schedulerMetrics; - private boolean metricsON; - private String metricsOutputDir; - private BufferedWriter metricsLogBW; - private boolean running = false; - private static Map defaultSchedulerMetricsMap = - new HashMap(); - static { - defaultSchedulerMetricsMap.put(FairScheduler.class, - FairSchedulerMetrics.class); - defaultSchedulerMetricsMap.put(FifoScheduler.class, - FifoSchedulerMetrics.class); - defaultSchedulerMetricsMap.put(CapacityScheduler.class, - CapacitySchedulerMetrics.class); - } - // must set by outside - private Set queueSet; - private Set trackedAppSet; - - public final Logger LOG = Logger.getLogger(ResourceSchedulerWrapper.class); - - public ResourceSchedulerWrapper() { - super(ResourceSchedulerWrapper.class.getName()); - samplerLock = new ReentrantLock(); - queueLock = new ReentrantLock(); - } - - @Override - public void setConf(Configuration conf) { - this.conf = conf; - // set scheduler - Class klass = conf.getClass( - SLSConfiguration.RM_SCHEDULER, null, ResourceScheduler.class); - - scheduler = ReflectionUtils.newInstance(klass, conf); - // start metrics - metricsON = conf.getBoolean(SLSConfiguration.METRICS_SWITCH, true); - if (metricsON) { - try { - initMetrics(); - } catch (Exception e) { - e.printStackTrace(); - } - } - - ShutdownHookManager.get().addShutdownHook(new Runnable() { - @Override - public void run() { - try { - if (metricsLogBW != null) { - metricsLogBW.write("]"); - metricsLogBW.close(); - } - if (web != null) { - web.stop(); - } - tearDown(); - } catch (Exception e) { - e.printStackTrace(); - } - } - }, SHUTDOWN_HOOK_PRIORITY); - } - - @Override - public Allocation allocate(ApplicationAttemptId attemptId, - List resourceRequests, List containerIds, - List strings, List strings2, - ContainerUpdates updateRequests) { - if (metricsON) { - final Timer.Context context = schedulerAllocateTimer.time(); - Allocation allocation = null; - try { - allocation = scheduler.allocate(attemptId, resourceRequests, - containerIds, strings, strings2, updateRequests); - return allocation; - } finally { - context.stop(); - schedulerAllocateCounter.inc(); - try { - updateQueueWithAllocateRequest(allocation, attemptId, - resourceRequests, containerIds); - } catch (IOException e) { - e.printStackTrace(); - } - } - } else { - return scheduler.allocate(attemptId, - resourceRequests, containerIds, strings, strings2, updateRequests); - } - } - - @Override - public void handle(SchedulerEvent schedulerEvent) { - // metrics off - if (! metricsON) { - scheduler.handle(schedulerEvent); - return; - } - if(!running) running = true; - - // metrics on - Timer.Context handlerTimer = null; - Timer.Context operationTimer = null; - - NodeUpdateSchedulerEventWrapper eventWrapper; - try { - //if (schedulerEvent instanceof NodeUpdateSchedulerEvent) { - if (schedulerEvent.getType() == SchedulerEventType.NODE_UPDATE - && schedulerEvent instanceof NodeUpdateSchedulerEvent) { - eventWrapper = new NodeUpdateSchedulerEventWrapper( - (NodeUpdateSchedulerEvent)schedulerEvent); - schedulerEvent = eventWrapper; - updateQueueWithNodeUpdate(eventWrapper); - } else if (schedulerEvent.getType() == SchedulerEventType.APP_ATTEMPT_REMOVED - && schedulerEvent instanceof AppAttemptRemovedSchedulerEvent) { - // check if having AM Container, update resource usage information - AppAttemptRemovedSchedulerEvent appRemoveEvent = - (AppAttemptRemovedSchedulerEvent) schedulerEvent; - ApplicationAttemptId appAttemptId = - appRemoveEvent.getApplicationAttemptID(); - String queue = appQueueMap.get(appAttemptId.getApplicationId()); - SchedulerAppReport app = scheduler.getSchedulerAppInfo(appAttemptId); - if (! app.getLiveContainers().isEmpty()) { // have 0 or 1 - // should have one container which is AM container - RMContainer rmc = app.getLiveContainers().iterator().next(); - updateQueueMetrics(queue, - rmc.getContainer().getResource().getMemorySize(), - rmc.getContainer().getResource().getVirtualCores()); - } - } - - handlerTimer = schedulerHandleTimer.time(); - operationTimer = schedulerHandleTimerMap - .get(schedulerEvent.getType()).time(); - - scheduler.handle(schedulerEvent); - } finally { - if (handlerTimer != null) handlerTimer.stop(); - if (operationTimer != null) operationTimer.stop(); - schedulerHandleCounter.inc(); - schedulerHandleCounterMap.get(schedulerEvent.getType()).inc(); - - if (schedulerEvent.getType() == SchedulerEventType.APP_REMOVED - && schedulerEvent instanceof AppRemovedSchedulerEvent) { - SLSRunner.decreaseRemainingApps(); - AppRemovedSchedulerEvent appRemoveEvent = - (AppRemovedSchedulerEvent) schedulerEvent; - appQueueMap.remove(appRemoveEvent.getApplicationID()); - } else if (schedulerEvent.getType() == SchedulerEventType.APP_ADDED - && schedulerEvent instanceof AppAddedSchedulerEvent) { - AppAddedSchedulerEvent appAddEvent = - (AppAddedSchedulerEvent) schedulerEvent; - String queueName = appAddEvent.getQueue(); - appQueueMap.put(appAddEvent.getApplicationId(), queueName); - } - } - } - - private void updateQueueWithNodeUpdate( - NodeUpdateSchedulerEventWrapper eventWrapper) { - RMNodeWrapper node = (RMNodeWrapper) eventWrapper.getRMNode(); - List containerList = node.getContainerUpdates(); - for (UpdatedContainerInfo info : containerList) { - for (ContainerStatus status : info.getCompletedContainers()) { - ContainerId containerId = status.getContainerId(); - SchedulerAppReport app = scheduler.getSchedulerAppInfo( - containerId.getApplicationAttemptId()); - - if (app == null) { - // this happens for the AM container - // The app have already removed when the NM sends the release - // information. - continue; - } - - String queue = - appQueueMap.get(containerId.getApplicationAttemptId() - .getApplicationId()); - int releasedMemory = 0, releasedVCores = 0; - if (status.getExitStatus() == ContainerExitStatus.SUCCESS) { - for (RMContainer rmc : app.getLiveContainers()) { - if (rmc.getContainerId() == containerId) { - releasedMemory += rmc.getContainer().getResource().getMemorySize(); - releasedVCores += rmc.getContainer() - .getResource().getVirtualCores(); - break; - } - } - } else if (status.getExitStatus() == ContainerExitStatus.ABORTED) { - if (preemptionContainerMap.containsKey(containerId)) { - Resource preResource = preemptionContainerMap.get(containerId); - releasedMemory += preResource.getMemorySize(); - releasedVCores += preResource.getVirtualCores(); - preemptionContainerMap.remove(containerId); - } - } - // update queue counters - updateQueueMetrics(queue, releasedMemory, releasedVCores); - } - } - } - - private void updateQueueWithAllocateRequest(Allocation allocation, - ApplicationAttemptId attemptId, - List resourceRequests, - List containerIds) throws IOException { - // update queue information - Resource pendingResource = Resources.createResource(0, 0); - Resource allocatedResource = Resources.createResource(0, 0); - String queueName = appQueueMap.get(attemptId.getApplicationId()); - // container requested - for (ResourceRequest request : resourceRequests) { - if (request.getResourceName().equals(ResourceRequest.ANY)) { - Resources.addTo(pendingResource, - Resources.multiply(request.getCapability(), - request.getNumContainers())); - } - } - // container allocated - for (Container container : allocation.getContainers()) { - Resources.addTo(allocatedResource, container.getResource()); - Resources.subtractFrom(pendingResource, container.getResource()); - } - // container released from AM - SchedulerAppReport report = scheduler.getSchedulerAppInfo(attemptId); - for (ContainerId containerId : containerIds) { - Container container = null; - for (RMContainer c : report.getLiveContainers()) { - if (c.getContainerId().equals(containerId)) { - container = c.getContainer(); - break; - } - } - if (container != null) { - // released allocated containers - Resources.subtractFrom(allocatedResource, container.getResource()); - } else { - for (RMContainer c : report.getReservedContainers()) { - if (c.getContainerId().equals(containerId)) { - container = c.getContainer(); - break; - } - } - if (container != null) { - // released reserved containers - Resources.subtractFrom(pendingResource, container.getResource()); - } - } - } - // containers released/preemption from scheduler - Set preemptionContainers = new HashSet(); - if (allocation.getContainerPreemptions() != null) { - preemptionContainers.addAll(allocation.getContainerPreemptions()); - } - if (allocation.getStrictContainerPreemptions() != null) { - preemptionContainers.addAll(allocation.getStrictContainerPreemptions()); - } - if (! preemptionContainers.isEmpty()) { - for (ContainerId containerId : preemptionContainers) { - if (! preemptionContainerMap.containsKey(containerId)) { - Container container = null; - for (RMContainer c : report.getLiveContainers()) { - if (c.getContainerId().equals(containerId)) { - container = c.getContainer(); - break; - } - } - if (container != null) { - preemptionContainerMap.put(containerId, container.getResource()); - } - } - - } - } - - // update metrics - SortedMap counterMap = metrics.getCounters(); - String names[] = new String[]{ - "counter.queue." + queueName + ".pending.memory", - "counter.queue." + queueName + ".pending.cores", - "counter.queue." + queueName + ".allocated.memory", - "counter.queue." + queueName + ".allocated.cores"}; - long values[] = new long[]{pendingResource.getMemorySize(), - pendingResource.getVirtualCores(), - allocatedResource.getMemorySize(), allocatedResource.getVirtualCores()}; - for (int i = names.length - 1; i >= 0; i --) { - if (! counterMap.containsKey(names[i])) { - metrics.counter(names[i]); - counterMap = metrics.getCounters(); - } - counterMap.get(names[i]).inc(values[i]); - } - - queueLock.lock(); - try { - if (! schedulerMetrics.isTracked(queueName)) { - schedulerMetrics.trackQueue(queueName); - } - } finally { - queueLock.unlock(); - } - } - - private void tearDown() throws IOException { - // close job runtime writer - if (jobRuntimeLogBW != null) { - jobRuntimeLogBW.close(); - } - // shut pool - if (pool != null) pool.shutdown(); - } - - @SuppressWarnings("unchecked") - private void initMetrics() throws Exception { - metrics = new MetricRegistry(); - // configuration - metricsOutputDir = conf.get(SLSConfiguration.METRICS_OUTPUT_DIR); - int metricsWebAddressPort = conf.getInt( - SLSConfiguration.METRICS_WEB_ADDRESS_PORT, - SLSConfiguration.METRICS_WEB_ADDRESS_PORT_DEFAULT); - // create SchedulerMetrics for current scheduler - String schedulerMetricsType = conf.get(scheduler.getClass().getName()); - Class schedulerMetricsClass = schedulerMetricsType == null? - defaultSchedulerMetricsMap.get(scheduler.getClass()) : - Class.forName(schedulerMetricsType); - schedulerMetrics = (SchedulerMetrics)ReflectionUtils - .newInstance(schedulerMetricsClass, new Configuration()); - schedulerMetrics.init(scheduler, metrics); - - // register various metrics - registerJvmMetrics(); - registerClusterResourceMetrics(); - registerContainerAppNumMetrics(); - registerSchedulerMetrics(); - - // .csv output - initMetricsCSVOutput(); - - // start web app to provide real-time tracking - web = new SLSWebApp(this, metricsWebAddressPort); - web.start(); - - // a thread to update histogram timer - pool = new ScheduledThreadPoolExecutor(2); - pool.scheduleAtFixedRate(new HistogramsRunnable(), 0, 1000, - TimeUnit.MILLISECONDS); - - // a thread to output metrics for real-tiem tracking - pool.scheduleAtFixedRate(new MetricsLogRunnable(), 0, 1000, - TimeUnit.MILLISECONDS); - - // application running information - jobRuntimeLogBW = - new BufferedWriter(new OutputStreamWriter(new FileOutputStream( - metricsOutputDir + "/jobruntime.csv"), "UTF-8")); - jobRuntimeLogBW.write("JobID,real_start_time,real_end_time," + - "simulate_start_time,simulate_end_time" + EOL); - jobRuntimeLogBW.flush(); - } - - private void registerJvmMetrics() { - // add JVM gauges - metrics.register("variable.jvm.free.memory", - new Gauge() { - @Override - public Long getValue() { - return Runtime.getRuntime().freeMemory(); - } - } - ); - metrics.register("variable.jvm.max.memory", - new Gauge() { - @Override - public Long getValue() { - return Runtime.getRuntime().maxMemory(); - } - } - ); - metrics.register("variable.jvm.total.memory", - new Gauge() { - @Override - public Long getValue() { - return Runtime.getRuntime().totalMemory(); - } - } - ); - } - - private void registerClusterResourceMetrics() { - metrics.register("variable.cluster.allocated.memory", - new Gauge() { - @Override - public Long getValue() { - if(scheduler == null || scheduler.getRootQueueMetrics() == null) { - return 0L; - } else { - return scheduler.getRootQueueMetrics().getAllocatedMB(); - } - } - } - ); - metrics.register("variable.cluster.allocated.vcores", - new Gauge() { - @Override - public Integer getValue() { - if(scheduler == null || scheduler.getRootQueueMetrics() == null) { - return 0; - } else { - return scheduler.getRootQueueMetrics().getAllocatedVirtualCores(); - } - } - } - ); - metrics.register("variable.cluster.available.memory", - new Gauge() { - @Override - public Long getValue() { - if(scheduler == null || scheduler.getRootQueueMetrics() == null) { - return 0L; - } else { - return scheduler.getRootQueueMetrics().getAvailableMB(); - } - } - } - ); - metrics.register("variable.cluster.available.vcores", - new Gauge() { - @Override - public Integer getValue() { - if(scheduler == null || scheduler.getRootQueueMetrics() == null) { - return 0; - } else { - return scheduler.getRootQueueMetrics().getAvailableVirtualCores(); - } - } - } - ); - } - - private void registerContainerAppNumMetrics() { - metrics.register("variable.running.application", - new Gauge() { - @Override - public Integer getValue() { - if (scheduler == null || scheduler.getRootQueueMetrics() == null) { - return 0; - } else { - return scheduler.getRootQueueMetrics().getAppsRunning(); - } - } - } - ); - metrics.register("variable.running.container", - new Gauge() { - @Override - public Integer getValue() { - if(scheduler == null || scheduler.getRootQueueMetrics() == null) { - return 0; - } else { - return scheduler.getRootQueueMetrics().getAllocatedContainers(); - } - } - } - ); - } - - private void registerSchedulerMetrics() { - samplerLock.lock(); - try { - // counters for scheduler operations - schedulerAllocateCounter = metrics.counter( - "counter.scheduler.operation.allocate"); - schedulerHandleCounter = metrics.counter( - "counter.scheduler.operation.handle"); - schedulerHandleCounterMap = new HashMap(); - for (SchedulerEventType e : SchedulerEventType.values()) { - Counter counter = metrics.counter( - "counter.scheduler.operation.handle." + e); - schedulerHandleCounterMap.put(e, counter); - } - // timers for scheduler operations - int timeWindowSize = conf.getInt( - SLSConfiguration.METRICS_TIMER_WINDOW_SIZE, - SLSConfiguration.METRICS_TIMER_WINDOW_SIZE_DEFAULT); - schedulerAllocateTimer = new Timer( - new SlidingWindowReservoir(timeWindowSize)); - schedulerHandleTimer = new Timer( - new SlidingWindowReservoir(timeWindowSize)); - schedulerHandleTimerMap = new HashMap(); - for (SchedulerEventType e : SchedulerEventType.values()) { - Timer timer = new Timer(new SlidingWindowReservoir(timeWindowSize)); - schedulerHandleTimerMap.put(e, timer); - } - // histogram for scheduler operations (Samplers) - schedulerHistogramList = new ArrayList(); - histogramTimerMap = new HashMap(); - Histogram schedulerAllocateHistogram = new Histogram( - new SlidingWindowReservoir(SAMPLING_SIZE)); - metrics.register("sampler.scheduler.operation.allocate.timecost", - schedulerAllocateHistogram); - schedulerHistogramList.add(schedulerAllocateHistogram); - histogramTimerMap.put(schedulerAllocateHistogram, schedulerAllocateTimer); - Histogram schedulerHandleHistogram = new Histogram( - new SlidingWindowReservoir(SAMPLING_SIZE)); - metrics.register("sampler.scheduler.operation.handle.timecost", - schedulerHandleHistogram); - schedulerHistogramList.add(schedulerHandleHistogram); - histogramTimerMap.put(schedulerHandleHistogram, schedulerHandleTimer); - for (SchedulerEventType e : SchedulerEventType.values()) { - Histogram histogram = new Histogram( - new SlidingWindowReservoir(SAMPLING_SIZE)); - metrics.register( - "sampler.scheduler.operation.handle." + e + ".timecost", - histogram); - schedulerHistogramList.add(histogram); - histogramTimerMap.put(histogram, schedulerHandleTimerMap.get(e)); - } - } finally { - samplerLock.unlock(); - } - } - - private void initMetricsCSVOutput() { - int timeIntervalMS = conf.getInt( - SLSConfiguration.METRICS_RECORD_INTERVAL_MS, - SLSConfiguration.METRICS_RECORD_INTERVAL_MS_DEFAULT); - File dir = new File(metricsOutputDir + "/metrics"); - if(! dir.exists() - && ! dir.mkdirs()) { - LOG.error("Cannot create directory " + dir.getAbsoluteFile()); - } - final CsvReporter reporter = CsvReporter.forRegistry(metrics) - .formatFor(Locale.US) - .convertRatesTo(TimeUnit.SECONDS) - .convertDurationsTo(TimeUnit.MILLISECONDS) - .build(new File(metricsOutputDir + "/metrics")); - reporter.start(timeIntervalMS, TimeUnit.MILLISECONDS); - } - - class HistogramsRunnable implements Runnable { - @Override - public void run() { - samplerLock.lock(); - try { - for (Histogram histogram : schedulerHistogramList) { - Timer timer = histogramTimerMap.get(histogram); - histogram.update((int) timer.getSnapshot().getMean()); - } - } finally { - samplerLock.unlock(); - } - } - } - - class MetricsLogRunnable implements Runnable { - private boolean firstLine = true; - public MetricsLogRunnable() { - try { - metricsLogBW = - new BufferedWriter(new OutputStreamWriter(new FileOutputStream( - metricsOutputDir + "/realtimetrack.json"), "UTF-8")); - metricsLogBW.write("["); - } catch (IOException e) { - e.printStackTrace(); - } - } - - @Override - public void run() { - if(running) { - // all WebApp to get real tracking json - String metrics = web.generateRealTimeTrackingMetrics(); - // output - try { - if(firstLine) { - metricsLogBW.write(metrics + EOL); - firstLine = false; - } else { - metricsLogBW.write("," + metrics + EOL); - } - metricsLogBW.flush(); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - } - - // the following functions are used by AMSimulator - public void addAMRuntime(ApplicationId appId, - long traceStartTimeMS, long traceEndTimeMS, - long simulateStartTimeMS, long simulateEndTimeMS) { - if (metricsON) { - try { - // write job runtime information - StringBuilder sb = new StringBuilder(); - sb.append(appId).append(",").append(traceStartTimeMS).append(",") - .append(traceEndTimeMS).append(",").append(simulateStartTimeMS) - .append(",").append(simulateEndTimeMS); - jobRuntimeLogBW.write(sb.toString() + EOL); - jobRuntimeLogBW.flush(); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - - private void updateQueueMetrics(String queue, - long releasedMemory, int releasedVCores) { - // update queue counters - SortedMap counterMap = metrics.getCounters(); - if (releasedMemory != 0) { - String name = "counter.queue." + queue + ".allocated.memory"; - if (! counterMap.containsKey(name)) { - metrics.counter(name); - counterMap = metrics.getCounters(); - } - counterMap.get(name).inc(-releasedMemory); - } - if (releasedVCores != 0) { - String name = "counter.queue." + queue + ".allocated.cores"; - if (! counterMap.containsKey(name)) { - metrics.counter(name); - counterMap = metrics.getCounters(); - } - counterMap.get(name).inc(-releasedVCores); - } - } - - public void setQueueSet(Set queues) { - this.queueSet = queues; - } - - public Set getQueueSet() { - return this.queueSet; - } - - public void setTrackedAppSet(Set apps) { - this.trackedAppSet = apps; - } - - public Set getTrackedAppSet() { - return this.trackedAppSet; - } - - public MetricRegistry getMetrics() { - return metrics; - } - - public SchedulerMetrics getSchedulerMetrics() { - return schedulerMetrics; - } - - // API open to out classes - public void addTrackedApp(ApplicationId appId, String oldAppId) { - if (metricsON) { - schedulerMetrics.trackApp(appId, oldAppId); - } - } - - public void removeTrackedApp(String oldAppId) { - if (metricsON) { - schedulerMetrics.untrackApp(oldAppId); - } - } - - @Override - public Configuration getConf() { - return conf; - } - - @SuppressWarnings("unchecked") - @Override - public void serviceInit(Configuration conf) throws Exception { - ((AbstractYarnScheduler) - scheduler).init(conf); - super.serviceInit(conf); - initScheduler(conf); - } - - private synchronized void initScheduler(Configuration configuration) throws - IOException { - this.applications = - new ConcurrentHashMap>(); - } - - @SuppressWarnings("unchecked") - @Override - public void serviceStart() throws Exception { - ((AbstractYarnScheduler) - scheduler).start(); - super.serviceStart(); - } - - @SuppressWarnings("unchecked") - @Override - public void serviceStop() throws Exception { - ((AbstractYarnScheduler) - scheduler).stop(); - super.serviceStop(); - } - - @Override - public void setRMContext(RMContext rmContext) { - scheduler.setRMContext(rmContext); - } - - @Override - public void reinitialize(Configuration conf, RMContext rmContext) - throws IOException { - scheduler.reinitialize(conf, rmContext); - } - - @Override - public void recover(RMStateStore.RMState rmState) throws Exception { - scheduler.recover(rmState); - } - - @Override - public QueueInfo getQueueInfo(String s, boolean b, boolean b2) - throws IOException { - return scheduler.getQueueInfo(s, b, b2); - } - - @Override - public List getQueueUserAclInfo() { - return scheduler.getQueueUserAclInfo(); - } - - @Override - public Resource getMinimumResourceCapability() { - return scheduler.getMinimumResourceCapability(); - } - - @Override - public Resource getMaximumResourceCapability() { - return scheduler.getMaximumResourceCapability(); - } - - @Override - public ResourceCalculator getResourceCalculator() { - return scheduler.getResourceCalculator(); - } - - @Override - public int getNumClusterNodes() { - return scheduler.getNumClusterNodes(); - } - - @Override - public SchedulerNodeReport getNodeReport(NodeId nodeId) { - return scheduler.getNodeReport(nodeId); - } - - @Override - public SchedulerAppReport getSchedulerAppInfo( - ApplicationAttemptId attemptId) { - return scheduler.getSchedulerAppInfo(attemptId); - } - - @Override - public QueueMetrics getRootQueueMetrics() { - return scheduler.getRootQueueMetrics(); - } - - @Override - public synchronized boolean checkAccess(UserGroupInformation callerUGI, - QueueACL acl, String queueName) { - return scheduler.checkAccess(callerUGI, acl, queueName); - } - - @Override - public ApplicationResourceUsageReport getAppResourceUsageReport( - ApplicationAttemptId appAttemptId) { - return scheduler.getAppResourceUsageReport(appAttemptId); - } - - @Override - public List getAppsInQueue(String queue) { - return scheduler.getAppsInQueue(queue); - } - - @Override - public RMContainer getRMContainer(ContainerId containerId) { - return null; - } - - @Override - public String moveApplication(ApplicationId appId, String newQueue) - throws YarnException { - return scheduler.moveApplication(appId, newQueue); - } - - @Override - @LimitedPrivate("yarn") - @Unstable - public Resource getClusterResource() { - return super.getClusterResource(); - } - - @Override - public synchronized List getTransferredContainers( - ApplicationAttemptId currentAttempt) { - return new ArrayList(); - } - - @Override - public Map> - getSchedulerApplications() { - return new HashMap>(); - } - - @Override - protected void completedContainerInternal(RMContainer rmContainer, - ContainerStatus containerStatus, RMContainerEventType event) { - // do nothing - } - - @Override - public Priority checkAndGetApplicationPriority(Priority priority, - UserGroupInformation user, String queueName, ApplicationId applicationId) - throws YarnException { - // TODO Dummy implementation. - return Priority.newInstance(0); - } - -} diff --git a/hadoop-tools/hadoop-sls/src/main/java/org/apache/hadoop/yarn/sls/scheduler/SLSCapacityScheduler.java b/hadoop-tools/hadoop-sls/src/main/java/org/apache/hadoop/yarn/sls/scheduler/SLSCapacityScheduler.java index 6ea2ab0f1e5..7c37465d7b6 100644 --- a/hadoop-tools/hadoop-sls/src/main/java/org/apache/hadoop/yarn/sls/scheduler/SLSCapacityScheduler.java +++ b/hadoop-tools/hadoop-sls/src/main/java/org/apache/hadoop/yarn/sls/scheduler/SLSCapacityScheduler.java @@ -17,34 +17,19 @@ */ package org.apache.hadoop.yarn.sls.scheduler; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.OutputStreamWriter; -import java.util.ArrayList; -import java.util.HashMap; import java.util.HashSet; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Set; -import java.util.SortedMap; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; import org.apache.hadoop.classification.InterfaceAudience.Private; import org.apache.hadoop.classification.InterfaceStability.Unstable; import org.apache.hadoop.conf.Configurable; import org.apache.hadoop.conf.Configuration; -import org.apache.hadoop.util.ReflectionUtils; import org.apache.hadoop.util.ShutdownHookManager; import org.apache.hadoop.yarn.api.records.ApplicationAttemptId; -import org.apache.hadoop.yarn.api.records.ApplicationId; import org.apache.hadoop.yarn.api.records.Container; import org.apache.hadoop.yarn.api.records.ContainerExitStatus; import org.apache.hadoop.yarn.api.records.ContainerId; @@ -65,117 +50,63 @@ import org.apache.hadoop.yarn.server.resourcemanager.scheduler.event.AppAttemptR import org.apache.hadoop.yarn.server.resourcemanager.scheduler.event.NodeUpdateSchedulerEvent; import org.apache.hadoop.yarn.server.resourcemanager.scheduler.event.SchedulerEvent; import org.apache.hadoop.yarn.server.resourcemanager.scheduler.event.SchedulerEventType; -import org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FairScheduler; -import org.apache.hadoop.yarn.server.resourcemanager.scheduler.fifo.FifoScheduler; import org.apache.hadoop.yarn.sls.SLSRunner; import org.apache.hadoop.yarn.sls.conf.SLSConfiguration; -import org.apache.hadoop.yarn.sls.web.SLSWebApp; +import org.apache.hadoop.yarn.sls.utils.SLSUtils; import org.apache.hadoop.yarn.util.resource.Resources; -import org.apache.log4j.Logger; -import com.codahale.metrics.Counter; -import com.codahale.metrics.CsvReporter; -import com.codahale.metrics.Gauge; -import com.codahale.metrics.Histogram; -import com.codahale.metrics.MetricRegistry; -import com.codahale.metrics.SlidingWindowReservoir; import com.codahale.metrics.Timer; @Private @Unstable public class SLSCapacityScheduler extends CapacityScheduler implements SchedulerWrapper,Configurable { - private static final String EOL = System.getProperty("line.separator"); - private static final String QUEUE_COUNTER_PREFIX = "counter.queue."; - private static final int SAMPLING_SIZE = 60; - private ScheduledExecutorService pool; - // counters for scheduler allocate/handle operations - private Counter schedulerAllocateCounter; - private Counter schedulerHandleCounter; - private Map schedulerHandleCounterMap; - // Timers for scheduler allocate/handle operations - private Timer schedulerAllocateTimer; - private Timer schedulerHandleTimer; - private Map schedulerHandleTimerMap; - private List schedulerHistogramList; - private Map histogramTimerMap; - private Lock samplerLock; - private Lock queueLock; private Configuration conf; private Map appQueueMap = new ConcurrentHashMap(); - private BufferedWriter jobRuntimeLogBW; - - // Priority of the ResourceSchedulerWrapper shutdown hook. - public static final int SHUTDOWN_HOOK_PRIORITY = 30; - - // web app - private SLSWebApp web; private Map preemptionContainerMap = new ConcurrentHashMap(); // metrics - private MetricRegistry metrics; private SchedulerMetrics schedulerMetrics; private boolean metricsON; - private String metricsOutputDir; - private BufferedWriter metricsLogBW; - private boolean running = false; - private static Map defaultSchedulerMetricsMap = - new HashMap(); - static { - defaultSchedulerMetricsMap.put(FairScheduler.class, - FairSchedulerMetrics.class); - defaultSchedulerMetricsMap.put(FifoScheduler.class, - FifoSchedulerMetrics.class); - defaultSchedulerMetricsMap.put(CapacityScheduler.class, - CapacitySchedulerMetrics.class); - } - // must set by outside - private Set queueSet; - private Set trackedAppSet; + private Tracker tracker; - public final Logger LOG = Logger.getLogger(SLSCapacityScheduler.class); + public Tracker getTracker() { + return tracker; + } public SLSCapacityScheduler() { - samplerLock = new ReentrantLock(); - queueLock = new ReentrantLock(); + tracker = new Tracker(); } @Override public void setConf(Configuration conf) { this.conf = conf; super.setConf(conf); - // start metrics metricsON = conf.getBoolean(SLSConfiguration.METRICS_SWITCH, true); if (metricsON) { try { - initMetrics(); + schedulerMetrics = SchedulerMetrics.getInstance(conf, + CapacityScheduler.class); + schedulerMetrics.init(this, conf); } catch (Exception e) { e.printStackTrace(); } - } - ShutdownHookManager.get().addShutdownHook(new Runnable() { - @Override - public void run() { - try { - if (metricsLogBW != null) { - metricsLogBW.write("]"); - metricsLogBW.close(); + ShutdownHookManager.get().addShutdownHook(new Runnable() { + @Override public void run() { + try { + schedulerMetrics.tearDown(); + } catch (Exception e) { + e.printStackTrace(); } - if (web != null) { - web.stop(); - } - tearDown(); - } catch (Exception e) { - e.printStackTrace(); } - } - }, SHUTDOWN_HOOK_PRIORITY); + }, SLSUtils.SHUTDOWN_HOOK_PRIORITY); + } } @Override @@ -184,7 +115,8 @@ public class SLSCapacityScheduler extends CapacityScheduler implements List strings, List strings2, ContainerUpdates updateRequests) { if (metricsON) { - final Timer.Context context = schedulerAllocateTimer.time(); + final Timer.Context context = schedulerMetrics.getSchedulerAllocateTimer() + .time(); Allocation allocation = null; try { allocation = super @@ -193,7 +125,7 @@ public class SLSCapacityScheduler extends CapacityScheduler implements return allocation; } finally { context.stop(); - schedulerAllocateCounter.inc(); + schedulerMetrics.increaseSchedulerAllocationCounter(); try { updateQueueWithAllocateRequest(allocation, attemptId, resourceRequests, containerIds); @@ -209,74 +141,76 @@ public class SLSCapacityScheduler extends CapacityScheduler implements @Override public void handle(SchedulerEvent schedulerEvent) { - // metrics off - if (! metricsON) { - super.handle(schedulerEvent); - return; - } - if(!running) running = true; + if (!metricsON) { + super.handle(schedulerEvent); + return; + } - // metrics on - Timer.Context handlerTimer = null; - Timer.Context operationTimer = null; + if (!schedulerMetrics.isRunning()) { + schedulerMetrics.setRunning(true); + } - NodeUpdateSchedulerEventWrapper eventWrapper; - try { - //if (schedulerEvent instanceof NodeUpdateSchedulerEvent) { - if (schedulerEvent.getType() == SchedulerEventType.NODE_UPDATE - && schedulerEvent instanceof NodeUpdateSchedulerEvent) { - eventWrapper = new NodeUpdateSchedulerEventWrapper( - (NodeUpdateSchedulerEvent)schedulerEvent); - schedulerEvent = eventWrapper; - updateQueueWithNodeUpdate(eventWrapper); - } else if (schedulerEvent.getType() == SchedulerEventType.APP_ATTEMPT_REMOVED - && schedulerEvent instanceof AppAttemptRemovedSchedulerEvent) { - // check if having AM Container, update resource usage information - AppAttemptRemovedSchedulerEvent appRemoveEvent = - (AppAttemptRemovedSchedulerEvent) schedulerEvent; - ApplicationAttemptId appAttemptId = - appRemoveEvent.getApplicationAttemptID(); - String queue = appQueueMap.get(appAttemptId); - SchedulerAppReport app = super.getSchedulerAppInfo(appAttemptId); - if (! app.getLiveContainers().isEmpty()) { // have 0 or 1 - // should have one container which is AM container - RMContainer rmc = app.getLiveContainers().iterator().next(); - updateQueueMetrics(queue, - rmc.getContainer().getResource().getMemorySize(), - rmc.getContainer().getResource().getVirtualCores()); - } - } + Timer.Context handlerTimer = null; + Timer.Context operationTimer = null; - handlerTimer = schedulerHandleTimer.time(); - operationTimer = schedulerHandleTimerMap - .get(schedulerEvent.getType()).time(); + NodeUpdateSchedulerEventWrapper eventWrapper; + try { + if (schedulerEvent.getType() == SchedulerEventType.NODE_UPDATE + && schedulerEvent instanceof NodeUpdateSchedulerEvent) { + eventWrapper = new NodeUpdateSchedulerEventWrapper( + (NodeUpdateSchedulerEvent)schedulerEvent); + schedulerEvent = eventWrapper; + updateQueueWithNodeUpdate(eventWrapper); + } else if (schedulerEvent.getType() == + SchedulerEventType.APP_ATTEMPT_REMOVED + && schedulerEvent instanceof AppAttemptRemovedSchedulerEvent) { + // check if having AM Container, update resource usage information + AppAttemptRemovedSchedulerEvent appRemoveEvent = + (AppAttemptRemovedSchedulerEvent) schedulerEvent; + ApplicationAttemptId appAttemptId = + appRemoveEvent.getApplicationAttemptID(); + String queue = appQueueMap.get(appAttemptId); + SchedulerAppReport app = super.getSchedulerAppInfo(appAttemptId); + if (!app.getLiveContainers().isEmpty()) { // have 0 or 1 + // should have one container which is AM container + RMContainer rmc = app.getLiveContainers().iterator().next(); + schedulerMetrics.updateQueueMetricsByRelease( + rmc.getContainer().getResource(), queue); + } + } - super.handle(schedulerEvent); - } finally { - if (handlerTimer != null) handlerTimer.stop(); - if (operationTimer != null) operationTimer.stop(); - schedulerHandleCounter.inc(); - schedulerHandleCounterMap.get(schedulerEvent.getType()).inc(); + handlerTimer = schedulerMetrics.getSchedulerHandleTimer().time(); + operationTimer = schedulerMetrics.getSchedulerHandleTimer( + schedulerEvent.getType()).time(); - if (schedulerEvent.getType() == SchedulerEventType.APP_ATTEMPT_REMOVED - && schedulerEvent instanceof AppAttemptRemovedSchedulerEvent) { - SLSRunner.decreaseRemainingApps(); - AppAttemptRemovedSchedulerEvent appRemoveEvent = - (AppAttemptRemovedSchedulerEvent) schedulerEvent; - ApplicationAttemptId appAttemptId = - appRemoveEvent.getApplicationAttemptID(); - appQueueMap.remove(appRemoveEvent.getApplicationAttemptID()); - } else if (schedulerEvent.getType() == SchedulerEventType.APP_ATTEMPT_ADDED - && schedulerEvent instanceof AppAttemptAddedSchedulerEvent) { - AppAttemptAddedSchedulerEvent appAddEvent = - (AppAttemptAddedSchedulerEvent) schedulerEvent; - SchedulerApplication app = - applications.get(appAddEvent.getApplicationAttemptId() + super.handle(schedulerEvent); + } finally { + if (handlerTimer != null) { + handlerTimer.stop(); + } + if (operationTimer != null) { + operationTimer.stop(); + } + schedulerMetrics.increaseSchedulerHandleCounter(schedulerEvent.getType()); + + if (schedulerEvent.getType() == SchedulerEventType.APP_ATTEMPT_REMOVED + && schedulerEvent instanceof AppAttemptRemovedSchedulerEvent) { + SLSRunner.decreaseRemainingApps(); + AppAttemptRemovedSchedulerEvent appRemoveEvent = + (AppAttemptRemovedSchedulerEvent) schedulerEvent; + appQueueMap.remove(appRemoveEvent.getApplicationAttemptID()); + } else if (schedulerEvent.getType() == + SchedulerEventType.APP_ATTEMPT_ADDED + && schedulerEvent instanceof AppAttemptAddedSchedulerEvent) { + AppAttemptAddedSchedulerEvent appAddEvent = + (AppAttemptAddedSchedulerEvent) schedulerEvent; + SchedulerApplication app = + applications.get(appAddEvent.getApplicationAttemptId() .getApplicationId()); - appQueueMap.put(appAddEvent.getApplicationAttemptId(), app.getQueue() - .getQueueName()); - } - } + appQueueMap.put(appAddEvent.getApplicationAttemptId(), app.getQueue() + .getQueueName()); + } + } } private void updateQueueWithNodeUpdate( @@ -316,7 +250,8 @@ public class SLSCapacityScheduler extends CapacityScheduler implements } } // update queue counters - updateQueueMetrics(queue, releasedMemory, releasedVCores); + schedulerMetrics.updateQueueMetricsByRelease( + Resource.newInstance(releasedMemory, releasedVCores), queue); } } } @@ -395,410 +330,13 @@ public class SLSCapacityScheduler extends CapacityScheduler implements } // update metrics - SortedMap counterMap = metrics.getCounters(); - String names[] = new String[]{ - "counter.queue." + queueName + ".pending.memory", - "counter.queue." + queueName + ".pending.cores", - "counter.queue." + queueName + ".allocated.memory", - "counter.queue." + queueName + ".allocated.cores"}; - long values[] = new long[]{pendingResource.getMemorySize(), - pendingResource.getVirtualCores(), - allocatedResource.getMemorySize(), allocatedResource.getVirtualCores()}; - for (int i = names.length - 1; i >= 0; i --) { - if (! counterMap.containsKey(names[i])) { - metrics.counter(names[i]); - counterMap = metrics.getCounters(); - } - counterMap.get(names[i]).inc(values[i]); - } - - queueLock.lock(); - try { - if (! schedulerMetrics.isTracked(queueName)) { - schedulerMetrics.trackQueue(queueName); - } - } finally { - queueLock.unlock(); - } - } - - private void tearDown() throws IOException { - // close job runtime writer - if (jobRuntimeLogBW != null) { - jobRuntimeLogBW.close(); - } - // shut pool - if (pool != null) pool.shutdown(); - } - - @SuppressWarnings({ "unchecked", "rawtypes" }) - private void initMetrics() throws Exception { - metrics = new MetricRegistry(); - // configuration - metricsOutputDir = conf.get(SLSConfiguration.METRICS_OUTPUT_DIR); - int metricsWebAddressPort = conf.getInt( - SLSConfiguration.METRICS_WEB_ADDRESS_PORT, - SLSConfiguration.METRICS_WEB_ADDRESS_PORT_DEFAULT); - // create SchedulerMetrics for current scheduler - String schedulerMetricsType = conf.get(CapacityScheduler.class.getName()); - Class schedulerMetricsClass = schedulerMetricsType == null? - defaultSchedulerMetricsMap.get(CapacityScheduler.class) : - Class.forName(schedulerMetricsType); - schedulerMetrics = (SchedulerMetrics)ReflectionUtils - .newInstance(schedulerMetricsClass, new Configuration()); - schedulerMetrics.init(this, metrics); - - // register various metrics - registerJvmMetrics(); - registerClusterResourceMetrics(); - registerContainerAppNumMetrics(); - registerSchedulerMetrics(); - - // .csv output - initMetricsCSVOutput(); - - // start web app to provide real-time tracking - web = new SLSWebApp(this, metricsWebAddressPort); - web.start(); - - // a thread to update histogram timer - pool = new ScheduledThreadPoolExecutor(2); - pool.scheduleAtFixedRate(new HistogramsRunnable(), 0, 1000, - TimeUnit.MILLISECONDS); - - // a thread to output metrics for real-tiem tracking - pool.scheduleAtFixedRate(new MetricsLogRunnable(), 0, 1000, - TimeUnit.MILLISECONDS); - - // application running information - jobRuntimeLogBW = - new BufferedWriter(new OutputStreamWriter(new FileOutputStream( - metricsOutputDir + "/jobruntime.csv"), "UTF-8")); - jobRuntimeLogBW.write("JobID,real_start_time,real_end_time," + - "simulate_start_time,simulate_end_time" + EOL); - jobRuntimeLogBW.flush(); - } - - private void registerJvmMetrics() { - // add JVM gauges - metrics.register("variable.jvm.free.memory", - new Gauge() { - @Override - public Long getValue() { - return Runtime.getRuntime().freeMemory(); - } - } - ); - metrics.register("variable.jvm.max.memory", - new Gauge() { - @Override - public Long getValue() { - return Runtime.getRuntime().maxMemory(); - } - } - ); - metrics.register("variable.jvm.total.memory", - new Gauge() { - @Override - public Long getValue() { - return Runtime.getRuntime().totalMemory(); - } - } - ); - } - - private void registerClusterResourceMetrics() { - metrics.register("variable.cluster.allocated.memory", - new Gauge() { - @Override - public Long getValue() { - if( getRootQueueMetrics() == null) { - return 0L; - } else { - return getRootQueueMetrics().getAllocatedMB(); - } - } - } - ); - metrics.register("variable.cluster.allocated.vcores", - new Gauge() { - @Override - public Integer getValue() { - if(getRootQueueMetrics() == null) { - return 0; - } else { - return getRootQueueMetrics().getAllocatedVirtualCores(); - } - } - } - ); - metrics.register("variable.cluster.available.memory", - new Gauge() { - @Override - public Long getValue() { - if(getRootQueueMetrics() == null) { - return 0L; - } else { - return getRootQueueMetrics().getAvailableMB(); - } - } - } - ); - metrics.register("variable.cluster.available.vcores", - new Gauge() { - @Override - public Integer getValue() { - if(getRootQueueMetrics() == null) { - return 0; - } else { - return getRootQueueMetrics().getAvailableVirtualCores(); - } - } - } - ); - metrics.register("variable.cluster.reserved.memory", - new Gauge() { - @Override - public Long getValue() { - if(getRootQueueMetrics() == null) { - return 0L; - } else { - return getRootQueueMetrics().getReservedMB(); - } - } - } - ); - metrics.register("variable.cluster.reserved.vcores", - new Gauge() { - @Override - public Integer getValue() { - if(getRootQueueMetrics() == null) { - return 0; - } else { - return getRootQueueMetrics().getReservedVirtualCores(); - } - } - } - ); - } - - private void registerContainerAppNumMetrics() { - metrics.register("variable.running.application", - new Gauge() { - @Override - public Integer getValue() { - if(getRootQueueMetrics() == null) { - return 0; - } else { - return getRootQueueMetrics().getAppsRunning(); - } - } - } - ); - metrics.register("variable.running.container", - new Gauge() { - @Override - public Integer getValue() { - if(getRootQueueMetrics() == null) { - return 0; - } else { - return getRootQueueMetrics().getAllocatedContainers(); - } - } - } - ); - } - - private void registerSchedulerMetrics() { - samplerLock.lock(); - try { - // counters for scheduler operations - schedulerAllocateCounter = metrics.counter( - "counter.scheduler.operation.allocate"); - schedulerHandleCounter = metrics.counter( - "counter.scheduler.operation.handle"); - schedulerHandleCounterMap = new HashMap(); - for (SchedulerEventType e : SchedulerEventType.values()) { - Counter counter = metrics.counter( - "counter.scheduler.operation.handle." + e); - schedulerHandleCounterMap.put(e, counter); - } - // timers for scheduler operations - int timeWindowSize = conf.getInt( - SLSConfiguration.METRICS_TIMER_WINDOW_SIZE, - SLSConfiguration.METRICS_TIMER_WINDOW_SIZE_DEFAULT); - schedulerAllocateTimer = new Timer( - new SlidingWindowReservoir(timeWindowSize)); - schedulerHandleTimer = new Timer( - new SlidingWindowReservoir(timeWindowSize)); - schedulerHandleTimerMap = new HashMap(); - for (SchedulerEventType e : SchedulerEventType.values()) { - Timer timer = new Timer(new SlidingWindowReservoir(timeWindowSize)); - schedulerHandleTimerMap.put(e, timer); - } - // histogram for scheduler operations (Samplers) - schedulerHistogramList = new ArrayList(); - histogramTimerMap = new HashMap(); - Histogram schedulerAllocateHistogram = new Histogram( - new SlidingWindowReservoir(SAMPLING_SIZE)); - metrics.register("sampler.scheduler.operation.allocate.timecost", - schedulerAllocateHistogram); - schedulerHistogramList.add(schedulerAllocateHistogram); - histogramTimerMap.put(schedulerAllocateHistogram, schedulerAllocateTimer); - Histogram schedulerHandleHistogram = new Histogram( - new SlidingWindowReservoir(SAMPLING_SIZE)); - metrics.register("sampler.scheduler.operation.handle.timecost", - schedulerHandleHistogram); - schedulerHistogramList.add(schedulerHandleHistogram); - histogramTimerMap.put(schedulerHandleHistogram, schedulerHandleTimer); - for (SchedulerEventType e : SchedulerEventType.values()) { - Histogram histogram = new Histogram( - new SlidingWindowReservoir(SAMPLING_SIZE)); - metrics.register( - "sampler.scheduler.operation.handle." + e + ".timecost", - histogram); - schedulerHistogramList.add(histogram); - histogramTimerMap.put(histogram, schedulerHandleTimerMap.get(e)); - } - } finally { - samplerLock.unlock(); - } - } - - private void initMetricsCSVOutput() { - int timeIntervalMS = conf.getInt( - SLSConfiguration.METRICS_RECORD_INTERVAL_MS, - SLSConfiguration.METRICS_RECORD_INTERVAL_MS_DEFAULT); - File dir = new File(metricsOutputDir + "/metrics"); - if(! dir.exists() - && ! dir.mkdirs()) { - LOG.error("Cannot create directory " + dir.getAbsoluteFile()); - } - final CsvReporter reporter = CsvReporter.forRegistry(metrics) - .formatFor(Locale.US) - .convertRatesTo(TimeUnit.SECONDS) - .convertDurationsTo(TimeUnit.MILLISECONDS) - .build(new File(metricsOutputDir + "/metrics")); - reporter.start(timeIntervalMS, TimeUnit.MILLISECONDS); - } - - class HistogramsRunnable implements Runnable { - @Override - public void run() { - samplerLock.lock(); - try { - for (Histogram histogram : schedulerHistogramList) { - Timer timer = histogramTimerMap.get(histogram); - histogram.update((int) timer.getSnapshot().getMean()); - } - } finally { - samplerLock.unlock(); - } - } - } - - class MetricsLogRunnable implements Runnable { - private boolean firstLine = true; - public MetricsLogRunnable() { - try { - metricsLogBW = - new BufferedWriter(new OutputStreamWriter(new FileOutputStream( - metricsOutputDir + "/realtimetrack.json"), "UTF-8")); - metricsLogBW.write("["); - } catch (IOException e) { - e.printStackTrace(); - } - } - - @Override - public void run() { - if(running) { - // all WebApp to get real tracking json - String metrics = web.generateRealTimeTrackingMetrics(); - // output - try { - if(firstLine) { - metricsLogBW.write(metrics + EOL); - firstLine = false; - } else { - metricsLogBW.write("," + metrics + EOL); - } - metricsLogBW.flush(); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - } - - // the following functions are used by AMSimulator - public void addAMRuntime(ApplicationId appId, - long traceStartTimeMS, long traceEndTimeMS, - long simulateStartTimeMS, long simulateEndTimeMS) { - - if (metricsON) { - try { - // write job runtime information - StringBuilder sb = new StringBuilder(); - sb.append(appId).append(",").append(traceStartTimeMS).append(",") - .append(traceEndTimeMS).append(",").append(simulateStartTimeMS) - .append(",").append(simulateEndTimeMS); - jobRuntimeLogBW.write(sb.toString() + EOL); - jobRuntimeLogBW.flush(); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - - private void updateQueueMetrics(String queue, - long releasedMemory, int releasedVCores) { - // update queue counters - SortedMap counterMap = metrics.getCounters(); - if (releasedMemory != 0) { - String name = "counter.queue." + queue + ".allocated.memory"; - if (! counterMap.containsKey(name)) { - metrics.counter(name); - counterMap = metrics.getCounters(); - } - counterMap.get(name).inc(-releasedMemory); - } - if (releasedVCores != 0) { - String name = "counter.queue." + queue + ".allocated.cores"; - if (! counterMap.containsKey(name)) { - metrics.counter(name); - counterMap = metrics.getCounters(); - } - counterMap.get(name).inc(-releasedVCores); - } + schedulerMetrics.updateQueueMetrics(pendingResource, allocatedResource, + queueName); } private void initQueueMetrics(CSQueue queue) { if (queue instanceof LeafQueue) { - SortedMap counterMap = metrics.getCounters(); - String queueName = queue.getQueueName(); - String[] names = new String[]{ - QUEUE_COUNTER_PREFIX + queueName + ".pending.memory", - QUEUE_COUNTER_PREFIX + queueName + ".pending.cores", - QUEUE_COUNTER_PREFIX + queueName + ".allocated.memory", - QUEUE_COUNTER_PREFIX + queueName + ".allocated.cores" }; - - for (int i = names.length - 1; i >= 0; i--) { - if (!counterMap.containsKey(names[i])) { - metrics.counter(names[i]); - counterMap = metrics.getCounters(); - } - } - - queueLock.lock(); - try { - if (!schedulerMetrics.isTracked(queueName)) { - schedulerMetrics.trackQueue(queueName); - } - } finally { - queueLock.unlock(); - } - + schedulerMetrics.initQueueMetric(queue.getQueueName()); return; } @@ -811,54 +349,17 @@ public class SLSCapacityScheduler extends CapacityScheduler implements public void serviceInit(Configuration configuration) throws Exception { super.serviceInit(configuration); - initQueueMetrics(getRootQueue()); - } - - public void setQueueSet(Set queues) { - this.queueSet = queues; - } - - public Set getQueueSet() { - return this.queueSet; - } - - public void setTrackedAppSet(Set apps) { - this.trackedAppSet = apps; - } - - public Set getTrackedAppSet() { - return this.trackedAppSet; - } - - public MetricRegistry getMetrics() { - return metrics; + if (metricsON) { + initQueueMetrics(getRootQueue()); + } } public SchedulerMetrics getSchedulerMetrics() { return schedulerMetrics; } - // API open to out classes - public void addTrackedApp(ApplicationId appId, - String oldAppId) { - if (metricsON) { - schedulerMetrics.trackApp(appId, oldAppId); - } - } - - public void removeTrackedApp(String oldAppId) { - if (metricsON) { - schedulerMetrics.untrackApp(oldAppId); - } - } - @Override public Configuration getConf() { return conf; } - - - - -} - +} \ No newline at end of file diff --git a/hadoop-tools/hadoop-sls/src/main/java/org/apache/hadoop/yarn/sls/scheduler/SLSFairScheduler.java b/hadoop-tools/hadoop-sls/src/main/java/org/apache/hadoop/yarn/sls/scheduler/SLSFairScheduler.java new file mode 100644 index 00000000000..572dacfc55d --- /dev/null +++ b/hadoop-tools/hadoop-sls/src/main/java/org/apache/hadoop/yarn/sls/scheduler/SLSFairScheduler.java @@ -0,0 +1,339 @@ +/** + * 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.yarn.sls.scheduler; + +import com.codahale.metrics.Timer; +import org.apache.hadoop.classification.InterfaceAudience.Private; +import org.apache.hadoop.classification.InterfaceStability.Unstable; +import org.apache.hadoop.conf.Configurable; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.util.ShutdownHookManager; +import org.apache.hadoop.yarn.api.records.ApplicationAttemptId; +import org.apache.hadoop.yarn.api.records.Container; +import org.apache.hadoop.yarn.api.records.ContainerExitStatus; +import org.apache.hadoop.yarn.api.records.ContainerId; +import org.apache.hadoop.yarn.api.records.ContainerStatus; +import org.apache.hadoop.yarn.api.records.Resource; +import org.apache.hadoop.yarn.api.records.ResourceRequest; +import org.apache.hadoop.yarn.server.resourcemanager.rmcontainer.RMContainer; +import org.apache.hadoop.yarn.server.resourcemanager.rmnode.UpdatedContainerInfo; +import org.apache.hadoop.yarn.server.resourcemanager.scheduler.Allocation; +import org.apache.hadoop.yarn.server.resourcemanager.scheduler.ContainerUpdates; +import org.apache.hadoop.yarn.server.resourcemanager.scheduler.SchedulerAppReport; +import org.apache.hadoop.yarn.server.resourcemanager.scheduler.event.AppAttemptRemovedSchedulerEvent; +import org.apache.hadoop.yarn.server.resourcemanager.scheduler.event.NodeUpdateSchedulerEvent; +import org.apache.hadoop.yarn.server.resourcemanager.scheduler.event.SchedulerEvent; +import org.apache.hadoop.yarn.server.resourcemanager.scheduler.event.SchedulerEventType; +import org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FSLeafQueue; +import org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FSQueue; +import org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FairScheduler; +import org.apache.hadoop.yarn.sls.SLSRunner; +import org.apache.hadoop.yarn.sls.conf.SLSConfiguration; +import org.apache.hadoop.yarn.sls.utils.SLSUtils; +import org.apache.hadoop.yarn.util.resource.Resources; + +import java.io.IOException; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +@Private +@Unstable +public class SLSFairScheduler extends FairScheduler + implements SchedulerWrapper, Configurable { + private SchedulerMetrics schedulerMetrics; + private boolean metricsON; + private Tracker tracker; + + private Map preemptionContainerMap = + new ConcurrentHashMap<>(); + + public SchedulerMetrics getSchedulerMetrics() { + return schedulerMetrics; + } + + public Tracker getTracker() { + return tracker; + } + + public SLSFairScheduler() { + tracker = new Tracker(); + } + + @Override + public void setConf(Configuration conf) { + super.setConfig(conf); + + metricsON = conf.getBoolean(SLSConfiguration.METRICS_SWITCH, true); + if (metricsON) { + try { + schedulerMetrics = SchedulerMetrics.getInstance(conf, + FairScheduler.class); + schedulerMetrics.init(this, conf); + } catch (Exception e) { + e.printStackTrace(); + } + + ShutdownHookManager.get().addShutdownHook(new Runnable() { + @Override public void run() { + try { + schedulerMetrics.tearDown(); + } catch (Exception e) { + e.printStackTrace(); + } + } + }, SLSUtils.SHUTDOWN_HOOK_PRIORITY); + } + } + + @Override + public Allocation allocate(ApplicationAttemptId attemptId, + List resourceRequests, List containerIds, + List blacklistAdditions, List blacklistRemovals, + ContainerUpdates updateRequests) { + if (metricsON) { + final Timer.Context context = schedulerMetrics.getSchedulerAllocateTimer() + .time(); + Allocation allocation = null; + try { + allocation = super.allocate(attemptId, resourceRequests, containerIds, + blacklistAdditions, blacklistRemovals, updateRequests); + return allocation; + } finally { + context.stop(); + schedulerMetrics.increaseSchedulerAllocationCounter(); + try { + updateQueueWithAllocateRequest(allocation, attemptId, + resourceRequests, containerIds); + } catch (IOException e) { + e.printStackTrace(); + } + } + } else { + return super.allocate(attemptId, resourceRequests, containerIds, + blacklistAdditions, blacklistRemovals, updateRequests); + } + } + + @Override + public void handle(SchedulerEvent schedulerEvent) { + // metrics off + if (!metricsON) { + super.handle(schedulerEvent); + return; + } + + // metrics on + if(!schedulerMetrics.isRunning()) { + schedulerMetrics.setRunning(true); + } + + Timer.Context handlerTimer = null; + Timer.Context operationTimer = null; + + NodeUpdateSchedulerEventWrapper eventWrapper; + try { + if (schedulerEvent.getType() == SchedulerEventType.NODE_UPDATE + && schedulerEvent instanceof NodeUpdateSchedulerEvent) { + eventWrapper = new NodeUpdateSchedulerEventWrapper( + (NodeUpdateSchedulerEvent)schedulerEvent); + schedulerEvent = eventWrapper; + updateQueueWithNodeUpdate(eventWrapper); + } else if ( + schedulerEvent.getType() == SchedulerEventType.APP_ATTEMPT_REMOVED + && schedulerEvent instanceof AppAttemptRemovedSchedulerEvent) { + // check if having AM Container, update resource usage information + AppAttemptRemovedSchedulerEvent appRemoveEvent = + (AppAttemptRemovedSchedulerEvent) schedulerEvent; + ApplicationAttemptId appAttemptId = + appRemoveEvent.getApplicationAttemptID(); + String queueName = getSchedulerApp(appAttemptId).getQueue().getName(); + SchedulerAppReport app = getSchedulerAppInfo(appAttemptId); + if (!app.getLiveContainers().isEmpty()) { // have 0 or 1 + // should have one container which is AM container + RMContainer rmc = app.getLiveContainers().iterator().next(); + schedulerMetrics.updateQueueMetricsByRelease( + rmc.getContainer().getResource(), queueName); + } + } + + handlerTimer = schedulerMetrics.getSchedulerHandleTimer().time(); + operationTimer = schedulerMetrics.getSchedulerHandleTimer( + schedulerEvent.getType()).time(); + + super.handle(schedulerEvent); + } finally { + if (handlerTimer != null) { + handlerTimer.stop(); + } + if (operationTimer != null) { + operationTimer.stop(); + } + schedulerMetrics.increaseSchedulerHandleCounter(schedulerEvent.getType()); + + if (schedulerEvent.getType() == SchedulerEventType.APP_ATTEMPT_REMOVED + && schedulerEvent instanceof AppAttemptRemovedSchedulerEvent) { + SLSRunner.decreaseRemainingApps(); + } + } + } + + private void updateQueueWithNodeUpdate( + NodeUpdateSchedulerEventWrapper eventWrapper) { + RMNodeWrapper node = (RMNodeWrapper) eventWrapper.getRMNode(); + List containerList = node.getContainerUpdates(); + for (UpdatedContainerInfo info : containerList) { + for (ContainerStatus status : info.getCompletedContainers()) { + ContainerId containerId = status.getContainerId(); + SchedulerAppReport app = super.getSchedulerAppInfo( + containerId.getApplicationAttemptId()); + + if (app == null) { + // this happens for the AM container + // The app have already removed when the NM sends the release + // information. + continue; + } + + int releasedMemory = 0, releasedVCores = 0; + if (status.getExitStatus() == ContainerExitStatus.SUCCESS) { + for (RMContainer rmc : app.getLiveContainers()) { + if (rmc.getContainerId() == containerId) { + Resource resource = rmc.getContainer().getResource(); + releasedMemory += resource.getMemorySize(); + releasedVCores += resource.getVirtualCores(); + break; + } + } + } else if (status.getExitStatus() == ContainerExitStatus.ABORTED) { + if (preemptionContainerMap.containsKey(containerId)) { + Resource preResource = preemptionContainerMap.get(containerId); + releasedMemory += preResource.getMemorySize(); + releasedVCores += preResource.getVirtualCores(); + preemptionContainerMap.remove(containerId); + } + } + // update queue counters + String queue = getSchedulerApp(containerId.getApplicationAttemptId()). + getQueueName(); + schedulerMetrics.updateQueueMetricsByRelease( + Resource.newInstance(releasedMemory, releasedVCores), queue); + } + } + } + + private void updateQueueWithAllocateRequest(Allocation allocation, + ApplicationAttemptId attemptId, + List resourceRequests, + List containerIds) throws IOException { + // update queue information + Resource pendingResource = Resources.createResource(0, 0); + Resource allocatedResource = Resources.createResource(0, 0); + // container requested + for (ResourceRequest request : resourceRequests) { + if (request.getResourceName().equals(ResourceRequest.ANY)) { + Resources.addTo(pendingResource, + Resources.multiply(request.getCapability(), + request.getNumContainers())); + } + } + // container allocated + for (Container container : allocation.getContainers()) { + Resources.addTo(allocatedResource, container.getResource()); + Resources.subtractFrom(pendingResource, container.getResource()); + } + // container released from AM + SchedulerAppReport report = super.getSchedulerAppInfo(attemptId); + for (ContainerId containerId : containerIds) { + Container container = null; + for (RMContainer c : report.getLiveContainers()) { + if (c.getContainerId().equals(containerId)) { + container = c.getContainer(); + break; + } + } + if (container != null) { + // released allocated containers + Resources.subtractFrom(allocatedResource, container.getResource()); + } else { + for (RMContainer c : report.getReservedContainers()) { + if (c.getContainerId().equals(containerId)) { + container = c.getContainer(); + break; + } + } + if (container != null) { + // released reserved containers + Resources.subtractFrom(pendingResource, container.getResource()); + } + } + } + // containers released/preemption from scheduler + Set preemptionContainers = new HashSet(); + if (allocation.getContainerPreemptions() != null) { + preemptionContainers.addAll(allocation.getContainerPreemptions()); + } + if (allocation.getStrictContainerPreemptions() != null) { + preemptionContainers.addAll(allocation.getStrictContainerPreemptions()); + } + if (!preemptionContainers.isEmpty()) { + for (ContainerId containerId : preemptionContainers) { + if (!preemptionContainerMap.containsKey(containerId)) { + Container container = null; + for (RMContainer c : report.getLiveContainers()) { + if (c.getContainerId().equals(containerId)) { + container = c.getContainer(); + break; + } + } + if (container != null) { + preemptionContainerMap.put(containerId, container.getResource()); + } + } + + } + } + + // update metrics + String queueName = getSchedulerApp(attemptId).getQueueName(); + schedulerMetrics.updateQueueMetrics(pendingResource, allocatedResource, + queueName); + } + + private void initQueueMetrics(FSQueue queue) { + if (queue instanceof FSLeafQueue) { + schedulerMetrics.initQueueMetric(queue.getQueueName()); + return; + } + + for (FSQueue child : queue.getChildQueues()) { + initQueueMetrics(child); + } + } + + @Override + public void serviceInit(Configuration conf) throws Exception { + super.serviceInit(conf); + if (metricsON) { + initQueueMetrics(getQueueManager().getRootQueue()); + } + } +} + diff --git a/hadoop-tools/hadoop-sls/src/main/java/org/apache/hadoop/yarn/sls/scheduler/SchedulerMetrics.java b/hadoop-tools/hadoop-sls/src/main/java/org/apache/hadoop/yarn/sls/scheduler/SchedulerMetrics.java index 8645a697c1d..a8792e81ec5 100644 --- a/hadoop-tools/hadoop-sls/src/main/java/org/apache/hadoop/yarn/sls/scheduler/SchedulerMetrics.java +++ b/hadoop-tools/hadoop-sls/src/main/java/org/apache/hadoop/yarn/sls/scheduler/SchedulerMetrics.java @@ -18,40 +18,170 @@ package org.apache.hadoop.yarn.sls.scheduler; +import java.io.BufferedWriter; +import java.io.IOException; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import java.util.Set; +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStreamWriter; +import java.util.ArrayList; +import java.util.List; +import java.util.SortedMap; +import java.util.Locale; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; +import java.util.concurrent.locks.Lock; +import com.codahale.metrics.Counter; +import com.codahale.metrics.CsvReporter; +import com.codahale.metrics.Gauge; +import com.codahale.metrics.Histogram; +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.SlidingWindowReservoir; +import com.codahale.metrics.Timer; import org.apache.hadoop.classification.InterfaceAudience.Private; import org.apache.hadoop.classification.InterfaceStability.Unstable; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.util.ReflectionUtils; import org.apache.hadoop.yarn.api.records.ApplicationId; +import org.apache.hadoop.yarn.api.records.Resource; import org.apache.hadoop.yarn.server.resourcemanager.scheduler.AbstractYarnScheduler; import org.apache.hadoop.yarn.server.resourcemanager.scheduler.ResourceScheduler; import org.apache.hadoop.yarn.server.resourcemanager.scheduler.SchedulerApplication; import org.apache.hadoop.yarn.server.resourcemanager.scheduler.SchedulerApplicationAttempt; - -import com.codahale.metrics.Gauge; -import com.codahale.metrics.MetricRegistry; +import org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CapacityScheduler; +import org.apache.hadoop.yarn.server.resourcemanager.scheduler.event.SchedulerEventType; +import org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FairScheduler; +import org.apache.hadoop.yarn.server.resourcemanager.scheduler.fifo.FifoScheduler; +import org.apache.hadoop.yarn.sls.conf.SLSConfiguration; +import org.apache.hadoop.yarn.sls.web.SLSWebApp; +import org.apache.log4j.Logger; @Private @Unstable public abstract class SchedulerMetrics { + private static final String EOL = System.getProperty("line.separator"); + private static final int SAMPLING_SIZE = 60; + private static final Logger LOG = Logger.getLogger(SchedulerMetrics.class); + protected ResourceScheduler scheduler; protected Set trackedQueues; protected MetricRegistry metrics; protected Set appTrackedMetrics; protected Set queueTrackedMetrics; - + + private Configuration conf; + private ScheduledExecutorService pool; + private SLSWebApp web; + + // metrics + private String metricsOutputDir; + private BufferedWriter metricsLogBW; + private BufferedWriter jobRuntimeLogBW; + private boolean running = false; + + // counters for scheduler allocate/handle operations + private Counter schedulerAllocateCounter; + private Counter schedulerHandleCounter; + private Map schedulerHandleCounterMap; + + // Timers for scheduler allocate/handle operations + private Timer schedulerAllocateTimer; + private Timer schedulerHandleTimer; + private Map schedulerHandleTimerMap; + private List schedulerHistogramList; + private Map histogramTimerMap; + private Lock samplerLock; + private Lock queueLock; + + static Class getSchedulerMetricsClass(Configuration conf, + Class schedulerClass) throws ClassNotFoundException { + Class metricClass = null; + String schedulerMetricsType = conf.get(schedulerClass.getName()); + if (schedulerMetricsType != null) { + metricClass = Class.forName(schedulerMetricsType); + } + + if (schedulerClass.equals(FairScheduler.class)) { + metricClass = FairSchedulerMetrics.class; + } else if (schedulerClass.equals(CapacityScheduler.class)) { + metricClass = CapacitySchedulerMetrics.class; + } else if (schedulerClass.equals(FifoScheduler.class)) { + metricClass = FifoSchedulerMetrics.class; + } + + return metricClass; + } + + static SchedulerMetrics getInstance(Configuration conf, Class schedulerClass) + throws ClassNotFoundException { + Class schedulerMetricClass = getSchedulerMetricsClass(conf, schedulerClass); + return (SchedulerMetrics) ReflectionUtils + .newInstance(schedulerMetricClass, new Configuration()); + } + public SchedulerMetrics() { + metrics = new MetricRegistry(); + appTrackedMetrics = new HashSet<>(); appTrackedMetrics.add("live.containers"); appTrackedMetrics.add("reserved.containers"); + queueTrackedMetrics = new HashSet<>(); + trackedQueues = new HashSet<>(); + + samplerLock = new ReentrantLock(); + queueLock = new ReentrantLock(); } - - public void init(ResourceScheduler scheduler, MetricRegistry metrics) { - this.scheduler = scheduler; - this.trackedQueues = new HashSet<>(); - this.metrics = metrics; + + void init(ResourceScheduler resourceScheduler, Configuration config) + throws Exception { + this.scheduler = resourceScheduler; + this.conf = config; + + metricsOutputDir = conf.get(SLSConfiguration.METRICS_OUTPUT_DIR); + + // register various metrics + registerJvmMetrics(); + registerClusterResourceMetrics(); + registerContainerAppNumMetrics(); + registerSchedulerMetrics(); + + // .csv output + initMetricsCSVOutput(); + + // start web app to provide real-time tracking + int metricsWebAddressPort = conf.getInt( + SLSConfiguration.METRICS_WEB_ADDRESS_PORT, + SLSConfiguration.METRICS_WEB_ADDRESS_PORT_DEFAULT); + web = new SLSWebApp((SchedulerWrapper)scheduler, metricsWebAddressPort); + web.start(); + + // a thread to update histogram timer + pool = new ScheduledThreadPoolExecutor(2); + pool.scheduleAtFixedRate(new HistogramsRunnable(), 0, 1000, + TimeUnit.MILLISECONDS); + + // a thread to output metrics for real-tiem tracking + pool.scheduleAtFixedRate(new MetricsLogRunnable(), 0, 1000, + TimeUnit.MILLISECONDS); + + // application running information + jobRuntimeLogBW = + new BufferedWriter(new OutputStreamWriter(new FileOutputStream( + metricsOutputDir + "/jobruntime.csv"), "UTF-8")); + jobRuntimeLogBW.write("JobID,real_start_time,real_end_time," + + "simulate_start_time,simulate_end_time" + EOL); + jobRuntimeLogBW.flush(); + } + + public MetricRegistry getMetrics() { + return metrics; } protected SchedulerApplicationAttempt getSchedulerAppAttempt( @@ -117,7 +247,392 @@ public abstract class SchedulerMetrics { public Set getAppTrackedMetrics() { return appTrackedMetrics; } + public Set getQueueTrackedMetrics() { return queueTrackedMetrics; } + + private void registerJvmMetrics() { + // add JVM gauges + metrics.register("variable.jvm.free.memory", + new Gauge() { + @Override + public Long getValue() { + return Runtime.getRuntime().freeMemory(); + } + } + ); + metrics.register("variable.jvm.max.memory", + new Gauge() { + @Override + public Long getValue() { + return Runtime.getRuntime().maxMemory(); + } + } + ); + metrics.register("variable.jvm.total.memory", + new Gauge() { + @Override + public Long getValue() { + return Runtime.getRuntime().totalMemory(); + } + } + ); + } + + private void registerClusterResourceMetrics() { + metrics.register("variable.cluster.allocated.memory", + new Gauge() { + @Override + public Long getValue() { + if (scheduler.getRootQueueMetrics() == null) { + return 0L; + } else { + return scheduler.getRootQueueMetrics().getAllocatedMB(); + } + } + } + ); + metrics.register("variable.cluster.allocated.vcores", + new Gauge() { + @Override + public Integer getValue() { + if (scheduler.getRootQueueMetrics() == null) { + return 0; + } else { + return scheduler.getRootQueueMetrics().getAllocatedVirtualCores(); + } + } + } + ); + metrics.register("variable.cluster.available.memory", + new Gauge() { + @Override + public Long getValue() { + if (scheduler.getRootQueueMetrics() == null) { + return 0L; + } else { + return scheduler.getRootQueueMetrics().getAvailableMB(); + } + } + } + ); + metrics.register("variable.cluster.available.vcores", + new Gauge() { + @Override + public Integer getValue() { + if (scheduler.getRootQueueMetrics() == null) { + return 0; + } else { + return scheduler.getRootQueueMetrics().getAvailableVirtualCores(); + } + } + } + ); + } + + private void registerContainerAppNumMetrics() { + metrics.register("variable.running.application", + new Gauge() { + @Override + public Integer getValue() { + if (scheduler.getRootQueueMetrics() == null) { + return 0; + } else { + return scheduler.getRootQueueMetrics().getAppsRunning(); + } + } + } + ); + metrics.register("variable.running.container", + new Gauge() { + @Override + public Integer getValue() { + if (scheduler.getRootQueueMetrics() == null) { + return 0; + } else { + return scheduler.getRootQueueMetrics().getAllocatedContainers(); + } + } + } + ); + } + + private void registerSchedulerMetrics() { + samplerLock.lock(); + try { + // counters for scheduler operations + schedulerAllocateCounter = metrics.counter( + "counter.scheduler.operation.allocate"); + schedulerHandleCounter = metrics.counter( + "counter.scheduler.operation.handle"); + schedulerHandleCounterMap = new HashMap<>(); + for (SchedulerEventType e : SchedulerEventType.values()) { + Counter counter = metrics.counter( + "counter.scheduler.operation.handle." + e); + schedulerHandleCounterMap.put(e, counter); + } + // timers for scheduler operations + int timeWindowSize = conf.getInt( + SLSConfiguration.METRICS_TIMER_WINDOW_SIZE, + SLSConfiguration.METRICS_TIMER_WINDOW_SIZE_DEFAULT); + schedulerAllocateTimer = new Timer( + new SlidingWindowReservoir(timeWindowSize)); + schedulerHandleTimer = new Timer( + new SlidingWindowReservoir(timeWindowSize)); + schedulerHandleTimerMap = new HashMap<>(); + for (SchedulerEventType e : SchedulerEventType.values()) { + Timer timer = new Timer(new SlidingWindowReservoir(timeWindowSize)); + schedulerHandleTimerMap.put(e, timer); + } + // histogram for scheduler operations (Samplers) + schedulerHistogramList = new ArrayList<>(); + histogramTimerMap = new HashMap<>(); + Histogram schedulerAllocateHistogram = new Histogram( + new SlidingWindowReservoir(SAMPLING_SIZE)); + metrics.register("sampler.scheduler.operation.allocate.timecost", + schedulerAllocateHistogram); + schedulerHistogramList.add(schedulerAllocateHistogram); + histogramTimerMap.put(schedulerAllocateHistogram, schedulerAllocateTimer); + Histogram schedulerHandleHistogram = new Histogram( + new SlidingWindowReservoir(SAMPLING_SIZE)); + metrics.register("sampler.scheduler.operation.handle.timecost", + schedulerHandleHistogram); + schedulerHistogramList.add(schedulerHandleHistogram); + histogramTimerMap.put(schedulerHandleHistogram, schedulerHandleTimer); + for (SchedulerEventType e : SchedulerEventType.values()) { + Histogram histogram = new Histogram( + new SlidingWindowReservoir(SAMPLING_SIZE)); + metrics.register( + "sampler.scheduler.operation.handle." + e + ".timecost", + histogram); + schedulerHistogramList.add(histogram); + histogramTimerMap.put(histogram, schedulerHandleTimerMap.get(e)); + } + } finally { + samplerLock.unlock(); + } + } + + private void initMetricsCSVOutput() { + int timeIntervalMS = conf.getInt( + SLSConfiguration.METRICS_RECORD_INTERVAL_MS, + SLSConfiguration.METRICS_RECORD_INTERVAL_MS_DEFAULT); + File dir = new File(metricsOutputDir + "/metrics"); + if(!dir.exists() && !dir.mkdirs()) { + LOG.error("Cannot create directory " + dir.getAbsoluteFile()); + } + final CsvReporter reporter = CsvReporter.forRegistry(metrics) + .formatFor(Locale.US) + .convertRatesTo(TimeUnit.SECONDS) + .convertDurationsTo(TimeUnit.MILLISECONDS) + .build(new File(metricsOutputDir + "/metrics")); + reporter.start(timeIntervalMS, TimeUnit.MILLISECONDS); + } + + boolean isRunning() { + return running; + } + + void setRunning(boolean running) { + this.running = running; + } + + class HistogramsRunnable implements Runnable { + @Override + public void run() { + samplerLock.lock(); + try { + for (Histogram histogram : schedulerHistogramList) { + Timer timer = histogramTimerMap.get(histogram); + histogram.update((int) timer.getSnapshot().getMean()); + } + } finally { + samplerLock.unlock(); + } + } + } + + class MetricsLogRunnable implements Runnable { + private boolean firstLine = true; + + MetricsLogRunnable() { + try { + metricsLogBW = + new BufferedWriter(new OutputStreamWriter(new FileOutputStream( + metricsOutputDir + "/realtimetrack.json"), "UTF-8")); + metricsLogBW.write("["); + } catch (IOException e) { + LOG.info(e.getMessage()); + } + } + + @Override + public void run() { + if(running) { + // all WebApp to get real tracking json + String trackingMetrics = web.generateRealTimeTrackingMetrics(); + // output + try { + if(firstLine) { + metricsLogBW.write(trackingMetrics + EOL); + firstLine = false; + } else { + metricsLogBW.write("," + trackingMetrics + EOL); + } + metricsLogBW.flush(); + } catch (IOException e) { + LOG.info(e.getMessage()); + } + } + } + } + + void tearDown() throws Exception { + if (metricsLogBW != null) { + metricsLogBW.write("]"); + metricsLogBW.close(); + } + + if (web != null) { + web.stop(); + } + + if (jobRuntimeLogBW != null) { + jobRuntimeLogBW.close(); + } + + if (pool != null) { + pool.shutdown(); + } + } + + void increaseSchedulerAllocationCounter() { + schedulerAllocateCounter.inc(); + } + + void increaseSchedulerHandleCounter(SchedulerEventType schedulerEventType) { + schedulerHandleCounter.inc(); + schedulerHandleCounterMap.get(schedulerEventType).inc(); + } + + Timer getSchedulerAllocateTimer() { + return schedulerAllocateTimer; + } + + Timer getSchedulerHandleTimer() { + return schedulerHandleTimer; + } + + Timer getSchedulerHandleTimer(SchedulerEventType schedulerEventType) { + return schedulerHandleTimerMap.get(schedulerEventType); + } + + private enum QueueMetric { + PENDING_MEMORY("pending.memory"), + PENDING_VCORES("pending.cores"), + ALLOCATED_MEMORY("allocated.memory"), + ALLOCATED_VCORES("allocated.cores"); + + private String value; + + QueueMetric(String value) { + this.value = value; + } + } + + private String getQueueMetricName(String queue, QueueMetric metric) { + return "counter.queue." + queue + "." + metric.value; + } + + private void traceQueueIfNotTraced(String queue) { + queueLock.lock(); + try { + if (!isTracked(queue)) { + trackQueue(queue); + } + } finally { + queueLock.unlock(); + } + } + + void initQueueMetric(String queueName){ + SortedMap counterMap = metrics.getCounters(); + + for (QueueMetric queueMetric : QueueMetric.values()) { + String metricName = getQueueMetricName(queueName, queueMetric); + if (!counterMap.containsKey(metricName)) { + metrics.counter(metricName); + counterMap = metrics.getCounters(); + } + } + + traceQueueIfNotTraced(queueName); + } + + void updateQueueMetrics(Resource pendingResource, Resource allocatedResource, + String queueName) { + SortedMap counterMap = metrics.getCounters(); + for(QueueMetric metric : QueueMetric.values()) { + String metricName = getQueueMetricName(queueName, metric); + if (!counterMap.containsKey(metricName)) { + metrics.counter(metricName); + counterMap = metrics.getCounters(); + } + + if (metric == QueueMetric.PENDING_MEMORY) { + counterMap.get(metricName).inc(pendingResource.getMemorySize()); + } else if (metric == QueueMetric.PENDING_VCORES) { + counterMap.get(metricName).inc(pendingResource.getVirtualCores()); + } else if (metric == QueueMetric.ALLOCATED_MEMORY) { + counterMap.get(metricName).inc(allocatedResource.getMemorySize()); + } else if (metric == QueueMetric.ALLOCATED_VCORES){ + counterMap.get(metricName).inc(allocatedResource.getVirtualCores()); + } + } + + traceQueueIfNotTraced(queueName); + } + + void updateQueueMetricsByRelease(Resource releaseResource, String queue) { + SortedMap counterMap = metrics.getCounters(); + String name = getQueueMetricName(queue, QueueMetric.ALLOCATED_MEMORY); + if (!counterMap.containsKey(name)) { + metrics.counter(name); + counterMap = metrics.getCounters(); + } + counterMap.get(name).inc(-releaseResource.getMemorySize()); + + String vcoreMetric = + getQueueMetricName(queue, QueueMetric.ALLOCATED_VCORES); + if (!counterMap.containsKey(vcoreMetric)) { + metrics.counter(vcoreMetric); + counterMap = metrics.getCounters(); + } + counterMap.get(vcoreMetric).inc(-releaseResource.getVirtualCores()); + } + + public void addTrackedApp(ApplicationId appId, + String oldAppId) { + trackApp(appId, oldAppId); + } + + public void removeTrackedApp(String oldAppId) { + untrackApp(oldAppId); + } + + public void addAMRuntime(ApplicationId appId, long traceStartTimeMS, + long traceEndTimeMS, long simulateStartTimeMS, long simulateEndTimeMS) { + try { + // write job runtime information + StringBuilder sb = new StringBuilder(); + sb.append(appId).append(",").append(traceStartTimeMS).append(",") + .append(traceEndTimeMS).append(",").append(simulateStartTimeMS) + .append(",").append(simulateEndTimeMS); + jobRuntimeLogBW.write(sb.toString() + EOL); + jobRuntimeLogBW.flush(); + } catch (IOException e) { + LOG.info(e.getMessage()); + } + } } diff --git a/hadoop-tools/hadoop-sls/src/main/java/org/apache/hadoop/yarn/sls/scheduler/SchedulerWrapper.java b/hadoop-tools/hadoop-sls/src/main/java/org/apache/hadoop/yarn/sls/scheduler/SchedulerWrapper.java index 962b137a7a5..406f0508eca 100644 --- a/hadoop-tools/hadoop-sls/src/main/java/org/apache/hadoop/yarn/sls/scheduler/SchedulerWrapper.java +++ b/hadoop-tools/hadoop-sls/src/main/java/org/apache/hadoop/yarn/sls/scheduler/SchedulerWrapper.java @@ -17,28 +17,12 @@ */ package org.apache.hadoop.yarn.sls.scheduler; -import java.util.Set; - import org.apache.hadoop.classification.InterfaceAudience.Private; import org.apache.hadoop.classification.InterfaceStability.Unstable; -import org.apache.hadoop.yarn.api.records.ApplicationId; - -import com.codahale.metrics.MetricRegistry; @Private @Unstable public interface SchedulerWrapper { - - MetricRegistry getMetrics(); SchedulerMetrics getSchedulerMetrics(); - Set getQueueSet(); - void setQueueSet(Set queues); - Set getTrackedAppSet(); - void setTrackedAppSet(Set apps); - void addTrackedApp(ApplicationId appId, String oldAppId); - void removeTrackedApp(String oldAppId); - void addAMRuntime(ApplicationId appId, - long traceStartTimeMS, long traceEndTimeMS, - long simulateStartTimeMS, long simulateEndTimeMS); - + Tracker getTracker(); } diff --git a/hadoop-tools/hadoop-sls/src/main/java/org/apache/hadoop/yarn/sls/scheduler/Tracker.java b/hadoop-tools/hadoop-sls/src/main/java/org/apache/hadoop/yarn/sls/scheduler/Tracker.java new file mode 100644 index 00000000000..42a5c3c894d --- /dev/null +++ b/hadoop-tools/hadoop-sls/src/main/java/org/apache/hadoop/yarn/sls/scheduler/Tracker.java @@ -0,0 +1,46 @@ +/** + * 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.yarn.sls.scheduler; + +import org.apache.hadoop.classification.InterfaceAudience.Private; +import org.apache.hadoop.classification.InterfaceStability.Unstable; + +import java.util.Set; + +@Private +@Unstable +public class Tracker { + private Set queueSet; + private Set trackedAppSet; + + public void setQueueSet(Set queues) { + queueSet = queues; + } + + public Set getQueueSet() { + return queueSet; + } + + public void setTrackedAppSet(Set apps) { + trackedAppSet = apps; + } + + public Set getTrackedAppSet() { + return trackedAppSet; + } +} diff --git a/hadoop-tools/hadoop-sls/src/main/java/org/apache/hadoop/yarn/sls/utils/SLSUtils.java b/hadoop-tools/hadoop-sls/src/main/java/org/apache/hadoop/yarn/sls/utils/SLSUtils.java index e5f7cd067b5..085edc00856 100644 --- a/hadoop-tools/hadoop-sls/src/main/java/org/apache/hadoop/yarn/sls/utils/SLSUtils.java +++ b/hadoop-tools/hadoop-sls/src/main/java/org/apache/hadoop/yarn/sls/utils/SLSUtils.java @@ -43,6 +43,7 @@ import org.apache.hadoop.tools.rumen.LoggedTaskAttempt; @Private @Unstable public class SLSUtils { + public static final int SHUTDOWN_HOOK_PRIORITY = 30; // hostname includes the network path and the host name. for example // "/default-rack/hostFoo" or "/coreSwitchA/TORSwitchB/hostBar". diff --git a/hadoop-tools/hadoop-sls/src/main/java/org/apache/hadoop/yarn/sls/web/SLSWebApp.java b/hadoop-tools/hadoop-sls/src/main/java/org/apache/hadoop/yarn/sls/web/SLSWebApp.java index 33d48466c20..2d2ffc55068 100644 --- a/hadoop-tools/hadoop-sls/src/main/java/org/apache/hadoop/yarn/sls/web/SLSWebApp.java +++ b/hadoop-tools/hadoop-sls/src/main/java/org/apache/hadoop/yarn/sls/web/SLSWebApp.java @@ -107,12 +107,12 @@ public class SLSWebApp extends HttpServlet { public SLSWebApp(SchedulerWrapper wrapper, int metricsAddressPort) { this.wrapper = wrapper; - metrics = wrapper.getMetrics(); handleOperTimecostHistogramMap = new HashMap(); queueAllocatedMemoryCounterMap = new HashMap(); queueAllocatedVCoresCounterMap = new HashMap(); schedulerMetrics = wrapper.getSchedulerMetrics(); + metrics = schedulerMetrics.getMetrics(); port = metricsAddressPort; } @@ -226,7 +226,7 @@ public class SLSWebApp extends HttpServlet { response.setStatus(HttpServletResponse.SC_OK); // queues {0} - Set queues = wrapper.getQueueSet(); + Set queues = wrapper.getTracker().getQueueSet(); StringBuilder queueInfo = new StringBuilder(); int i = 0; @@ -265,7 +265,7 @@ public class SLSWebApp extends HttpServlet { // tracked queues {0} StringBuilder trackedQueueInfo = new StringBuilder(); - Set trackedQueues = wrapper.getQueueSet(); + Set trackedQueues = wrapper.getTracker().getQueueSet(); for(String queue : trackedQueues) { trackedQueueInfo.append(""); @@ -273,7 +273,7 @@ public class SLSWebApp extends HttpServlet { // tracked apps {1} StringBuilder trackedAppInfo = new StringBuilder(); - Set trackedApps = wrapper.getTrackedAppSet(); + Set trackedApps = wrapper.getTracker().getTrackedAppSet(); for(String job : trackedApps) { trackedAppInfo.append(""); @@ -422,7 +422,7 @@ public class SLSWebApp extends HttpServlet { // allocated resource for each queue Map queueAllocatedMemoryMap = new HashMap(); Map queueAllocatedVCoresMap = new HashMap(); - for (String queue : wrapper.getQueueSet()) { + for (String queue : wrapper.getTracker().getQueueSet()) { // memory String key = "counter.queue." + queue + ".allocated.memory"; if (! queueAllocatedMemoryCounterMap.containsKey(queue) && @@ -462,7 +462,7 @@ public class SLSWebApp extends HttpServlet { .append(",\"cluster.available.memory\":").append(availableMemoryGB) .append(",\"cluster.available.vcores\":").append(availableVCoresGB); - for (String queue : wrapper.getQueueSet()) { + for (String queue : wrapper.getTracker().getQueueSet()) { sb.append(",\"queue.").append(queue).append(".allocated.memory\":") .append(queueAllocatedMemoryMap.get(queue)); sb.append(",\"queue.").append(queue).append(".allocated.vcores\":") diff --git a/hadoop-tools/hadoop-sls/src/test/java/org/apache/hadoop/yarn/sls/appmaster/TestAMSimulator.java b/hadoop-tools/hadoop-sls/src/test/java/org/apache/hadoop/yarn/sls/appmaster/TestAMSimulator.java index f0d8e6f507b..ca3d1958a3b 100644 --- a/hadoop-tools/hadoop-sls/src/test/java/org/apache/hadoop/yarn/sls/appmaster/TestAMSimulator.java +++ b/hadoop-tools/hadoop-sls/src/test/java/org/apache/hadoop/yarn/sls/appmaster/TestAMSimulator.java @@ -22,37 +22,56 @@ import org.apache.commons.io.FileUtils; import org.apache.hadoop.yarn.conf.YarnConfiguration; import org.apache.hadoop.yarn.exceptions.YarnException; import org.apache.hadoop.yarn.server.resourcemanager.ResourceManager; +import org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CapacityScheduler; +import org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FairScheduler; import org.apache.hadoop.yarn.sls.conf.SLSConfiguration; -import org.apache.hadoop.yarn.sls.scheduler.ContainerSimulator; -import org.apache.hadoop.yarn.sls.scheduler.FairSchedulerMetrics; -import org.apache.hadoop.yarn.sls.scheduler.SchedulerWrapper; +import org.apache.hadoop.yarn.sls.scheduler.*; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.List; +@RunWith(Parameterized.class) public class TestAMSimulator { private ResourceManager rm; private YarnConfiguration conf; private Path metricOutputDir; + private Class slsScheduler; + private Class scheduler; + + @Parameterized.Parameters + public static Collection params() { + return Arrays.asList(new Object[][] { + {SLSFairScheduler.class, FairScheduler.class}, + {SLSCapacityScheduler.class, CapacityScheduler.class} + }); + } + + public TestAMSimulator(Class slsScheduler, Class scheduler) { + this.slsScheduler = slsScheduler; + this.scheduler = scheduler; + } + @Before public void setup() { createMetricOutputDir(); conf = new YarnConfiguration(); conf.set(SLSConfiguration.METRICS_OUTPUT_DIR, metricOutputDir.toString()); - conf.set(YarnConfiguration.RM_SCHEDULER, - "org.apache.hadoop.yarn.sls.scheduler.ResourceSchedulerWrapper"); - conf.set(SLSConfiguration.RM_SCHEDULER, - "org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FairScheduler"); + conf.set(YarnConfiguration.RM_SCHEDULER, slsScheduler.getName()); + conf.set(SLSConfiguration.RM_SCHEDULER, scheduler.getName()); conf.setBoolean(SLSConfiguration.METRICS_SWITCH, true); rm = new ResourceManager(); rm.init(conf); @@ -76,15 +95,17 @@ public class TestAMSimulator { } private void verifySchedulerMetrics(String appId) { - SchedulerWrapper schedulerWrapper = (SchedulerWrapper) - rm.getResourceScheduler(); - MetricRegistry metricRegistry = schedulerWrapper.getMetrics(); - for (FairSchedulerMetrics.Metric metric : - FairSchedulerMetrics.Metric.values()) { - String key = "variable.app." + appId + "." + metric.getValue() - + ".memory"; - Assert.assertTrue(metricRegistry.getGauges().containsKey(key)); - Assert.assertNotNull(metricRegistry.getGauges().get(key).getValue()); + if (scheduler.equals(FairScheduler.class)) { + SchedulerMetrics schedulerMetrics = ((SchedulerWrapper) + rm.getResourceScheduler()).getSchedulerMetrics(); + MetricRegistry metricRegistry = schedulerMetrics.getMetrics(); + for (FairSchedulerMetrics.Metric metric : + FairSchedulerMetrics.Metric.values()) { + String key = "variable.app." + appId + "." + metric.getValue() + + ".memory"; + Assert.assertTrue(metricRegistry.getGauges().containsKey(key)); + Assert.assertNotNull(metricRegistry.getGauges().get(key).getValue()); + } } } diff --git a/hadoop-tools/hadoop-sls/src/test/java/org/apache/hadoop/yarn/sls/nodemanager/TestNMSimulator.java b/hadoop-tools/hadoop-sls/src/test/java/org/apache/hadoop/yarn/sls/nodemanager/TestNMSimulator.java index f9a393298eb..2f10f7dbc2e 100644 --- a/hadoop-tools/hadoop-sls/src/test/java/org/apache/hadoop/yarn/sls/nodemanager/TestNMSimulator.java +++ b/hadoop-tools/hadoop-sls/src/test/java/org/apache/hadoop/yarn/sls/nodemanager/TestNMSimulator.java @@ -21,26 +21,50 @@ import org.apache.hadoop.yarn.api.records.Container; import org.apache.hadoop.yarn.api.records.ContainerId; import org.apache.hadoop.yarn.conf.YarnConfiguration; import org.apache.hadoop.yarn.server.resourcemanager.ResourceManager; +import org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CapacityScheduler; +import org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FairScheduler; import org.apache.hadoop.yarn.server.utils.BuilderUtils; import org.apache.hadoop.yarn.sls.conf.SLSConfiguration; +import org.apache.hadoop.yarn.sls.scheduler.SLSCapacityScheduler; +import org.apache.hadoop.yarn.sls.scheduler.SLSFairScheduler; import org.apache.hadoop.yarn.util.resource.Resources; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import java.util.Arrays; +import java.util.Collection; + +@RunWith(Parameterized.class) public class TestNMSimulator { private final int GB = 1024; private ResourceManager rm; private YarnConfiguration conf; + private Class slsScheduler; + private Class scheduler; + + @Parameterized.Parameters + public static Collection params() { + return Arrays.asList(new Object[][] { + {SLSFairScheduler.class, FairScheduler.class}, + {SLSCapacityScheduler.class, CapacityScheduler.class} + }); + } + + public TestNMSimulator(Class slsScheduler, Class scheduler) { + this.slsScheduler = slsScheduler; + this.scheduler = scheduler; + } + @Before public void setup() { conf = new YarnConfiguration(); - conf.set(YarnConfiguration.RM_SCHEDULER, - "org.apache.hadoop.yarn.sls.scheduler.ResourceSchedulerWrapper"); - conf.set(SLSConfiguration.RM_SCHEDULER, - "org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FairScheduler"); + conf.set(YarnConfiguration.RM_SCHEDULER, slsScheduler.getName()); + conf.set(SLSConfiguration.RM_SCHEDULER, scheduler.getName()); conf.setBoolean(SLSConfiguration.METRICS_SWITCH, false); rm = new ResourceManager(); rm.init(conf); From ceab00ac62f8057a07b4b936799e6f04271e6e41 Mon Sep 17 00:00:00 2001 From: Ming Ma Date: Wed, 29 Mar 2017 17:41:58 -0700 Subject: [PATCH 160/188] MAPREDUCE-6862. Fragments are not handled correctly by resource limit checking. (Chris Trezzo via mingma) --- .../hadoop/mapreduce/JobResourceUploader.java | 36 ++++++++++++----- .../mapreduce/TestJobResourceUploader.java | 40 ++++++++++++++++--- 2 files changed, 59 insertions(+), 17 deletions(-) diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/JobResourceUploader.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/JobResourceUploader.java index 4c48ff48c5a..085c96612d9 100644 --- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/JobResourceUploader.java +++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/JobResourceUploader.java @@ -238,28 +238,42 @@ class JobResourceUploader { Collection dcArchives = conf.getStringCollection(MRJobConfig.CACHE_ARCHIVES); - for (String path : dcFiles) { - explorePath(conf, new Path(path), limitChecker, statCache); + for (String uri : dcFiles) { + explorePath(conf, stringToPath(uri), limitChecker, statCache); } - for (String path : dcArchives) { - explorePath(conf, new Path(path), limitChecker, statCache); + for (String uri : dcArchives) { + explorePath(conf, stringToPath(uri), limitChecker, statCache); } - for (String path : files) { - explorePath(conf, new Path(path), limitChecker, statCache); + for (String uri : files) { + explorePath(conf, stringToPath(uri), limitChecker, statCache); } - for (String path : libjars) { - explorePath(conf, new Path(path), limitChecker, statCache); + for (String uri : libjars) { + explorePath(conf, stringToPath(uri), limitChecker, statCache); } - for (String path : archives) { - explorePath(conf, new Path(path), limitChecker, statCache); + for (String uri : archives) { + explorePath(conf, stringToPath(uri), limitChecker, statCache); } if (jobJar != null) { - explorePath(conf, new Path(jobJar), limitChecker, statCache); + explorePath(conf, stringToPath(jobJar), limitChecker, statCache); + } + } + + /** + * Convert a String to a Path and gracefully remove fragments/queries if they + * exist in the String. + */ + @VisibleForTesting + Path stringToPath(String s) { + try { + URI uri = new URI(s); + return new Path(uri.getScheme(), uri.getAuthority(), uri.getPath()); + } catch (URISyntaxException e) { + throw new IllegalArgumentException(e); } } diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/test/java/org/apache/hadoop/mapreduce/TestJobResourceUploader.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/test/java/org/apache/hadoop/mapreduce/TestJobResourceUploader.java index 36ea57af21d..8ba50a66b3c 100644 --- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/test/java/org/apache/hadoop/mapreduce/TestJobResourceUploader.java +++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/test/java/org/apache/hadoop/mapreduce/TestJobResourceUploader.java @@ -39,6 +39,34 @@ import org.junit.Test; */ public class TestJobResourceUploader { + @Test + public void testStringToPath() throws IOException { + Configuration conf = new Configuration(); + JobResourceUploader uploader = + new JobResourceUploader(FileSystem.getLocal(conf), false); + + Assert.assertEquals("Failed: absolute, no scheme, with fragment", + "/testWithFragment.txt", + uploader.stringToPath("/testWithFragment.txt#fragment.txt").toString()); + + Assert.assertEquals("Failed: absolute, with scheme, with fragment", + "file:/testWithFragment.txt", + uploader.stringToPath("file:///testWithFragment.txt#fragment.txt") + .toString()); + + Assert.assertEquals("Failed: relative, no scheme, with fragment", + "testWithFragment.txt", + uploader.stringToPath("testWithFragment.txt#fragment.txt").toString()); + + Assert.assertEquals("Failed: relative, no scheme, no fragment", + "testWithFragment.txt", + uploader.stringToPath("testWithFragment.txt").toString()); + + Assert.assertEquals("Failed: absolute, with scheme, no fragment", + "file:/testWithFragment.txt", + uploader.stringToPath("file:///testWithFragment.txt").toString()); + } + @Test public void testAllDefaults() throws IOException { ResourceLimitsConf.Builder b = new ResourceLimitsConf.Builder(); @@ -210,17 +238,17 @@ public class TestJobResourceUploader { rlConf.maxSingleResourceMB); conf.set("tmpfiles", - buildPathString("file://tmpFiles", rlConf.numOfTmpFiles)); + buildPathString("file:///tmpFiles", rlConf.numOfTmpFiles)); conf.set("tmpjars", - buildPathString("file://tmpjars", rlConf.numOfTmpLibJars)); + buildPathString("file:///tmpjars", rlConf.numOfTmpLibJars)); conf.set("tmparchives", - buildPathString("file://tmpArchives", rlConf.numOfTmpArchives)); + buildPathString("file:///tmpArchives", rlConf.numOfTmpArchives)); conf.set(MRJobConfig.CACHE_ARCHIVES, - buildPathString("file://cacheArchives", rlConf.numOfDCArchives)); + buildPathString("file:///cacheArchives", rlConf.numOfDCArchives)); conf.set(MRJobConfig.CACHE_FILES, - buildPathString("file://cacheFiles", rlConf.numOfDCFiles)); + buildPathString("file:///cacheFiles", rlConf.numOfDCFiles)); if (rlConf.jobJar) { - conf.setJar("file://jobjar.jar"); + conf.setJar("file:///jobjar.jar"); } return conf; } From de7efd2687ccb74608249abf4c83c84a9345ec0a Mon Sep 17 00:00:00 2001 From: Zhe Zhang Date: Wed, 29 Mar 2017 22:10:55 -0700 Subject: [PATCH 161/188] MAPREDUCE-6873. MR Job Submission Fails if MR framework application path not on defaultFS. Contributed by Erik Krogen. --- .../src/main/java/org/apache/hadoop/mapreduce/JobSubmitter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/JobSubmitter.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/JobSubmitter.java index 09edd948389..6ade376f01d 100644 --- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/JobSubmitter.java +++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/JobSubmitter.java @@ -458,7 +458,7 @@ class JobSubmitter { // resolve any symlinks in the URI path so using a "current" symlink // to point to a specific version shows the specific version // in the distributed cache configuration - FileSystem fs = FileSystem.get(conf); + FileSystem fs = FileSystem.get(uri, conf); Path frameworkPath = fs.makeQualified( new Path(uri.getScheme(), uri.getAuthority(), uri.getPath())); FileContext fc = FileContext.getFileContext(frameworkPath.toUri(), conf); From 0d053eeb3002f348d34cf3aac5b43333af8ff10a Mon Sep 17 00:00:00 2001 From: Akira Ajisaka Date: Thu, 30 Mar 2017 18:11:50 +0900 Subject: [PATCH 162/188] HADOOP-14256. [S3A DOC] Correct the format for "Seoul" example. Contributed by Brahma Reddy Battula. --- .../hadoop-aws/src/site/markdown/tools/hadoop-aws/index.md | 1 + 1 file changed, 1 insertion(+) diff --git a/hadoop-tools/hadoop-aws/src/site/markdown/tools/hadoop-aws/index.md b/hadoop-tools/hadoop-aws/src/site/markdown/tools/hadoop-aws/index.md index 3e99656a8ee..82c35885301 100644 --- a/hadoop-tools/hadoop-aws/src/site/markdown/tools/hadoop-aws/index.md +++ b/hadoop-tools/hadoop-aws/src/site/markdown/tools/hadoop-aws/index.md @@ -1024,6 +1024,7 @@ Frankfurt ``` Seoul + ```xml fs.s3a.endpoint From c8bd5fc7a86f9890ceaa37a89491ab650e7e9a64 Mon Sep 17 00:00:00 2001 From: Jason Lowe Date: Thu, 30 Mar 2017 10:57:19 -0500 Subject: [PATCH 163/188] MAPREDUCE-6850. Shuffle Handler keep-alive connections are closed from the server side. Contributed by Jonathan Eagles --- .../apache/hadoop/mapred/ShuffleHandler.java | 68 +++++++++++++++++-- .../hadoop/mapred/TestShuffleHandler.java | 31 +++++++++ 2 files changed, 92 insertions(+), 7 deletions(-) diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-shuffle/src/main/java/org/apache/hadoop/mapred/ShuffleHandler.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-shuffle/src/main/java/org/apache/hadoop/mapred/ShuffleHandler.java index 15a1b89f84a..863da7efde9 100644 --- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-shuffle/src/main/java/org/apache/hadoop/mapred/ShuffleHandler.java +++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-shuffle/src/main/java/org/apache/hadoop/mapred/ShuffleHandler.java @@ -104,6 +104,7 @@ import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelFactory; import org.jboss.netty.channel.ChannelFuture; import org.jboss.netty.channel.ChannelFutureListener; +import org.jboss.netty.channel.ChannelHandler; import org.jboss.netty.channel.ChannelHandlerContext; import org.jboss.netty.channel.ChannelPipeline; import org.jboss.netty.channel.ChannelPipelineFactory; @@ -126,7 +127,13 @@ import org.jboss.netty.handler.codec.http.HttpResponseStatus; import org.jboss.netty.handler.codec.http.QueryStringDecoder; import org.jboss.netty.handler.ssl.SslHandler; import org.jboss.netty.handler.stream.ChunkedWriteHandler; +import org.jboss.netty.handler.timeout.IdleState; +import org.jboss.netty.handler.timeout.IdleStateAwareChannelHandler; +import org.jboss.netty.handler.timeout.IdleStateEvent; +import org.jboss.netty.handler.timeout.IdleStateHandler; import org.jboss.netty.util.CharsetUtil; +import org.jboss.netty.util.HashedWheelTimer; +import org.jboss.netty.util.Timer; import org.eclipse.jetty.http.HttpHeader; import com.google.common.annotations.VisibleForTesting; @@ -240,6 +247,7 @@ public class ShuffleHandler extends AuxiliaryService { public static final boolean DEFAULT_SHUFFLE_TRANSFERTO_ALLOWED = true; public static final boolean WINDOWS_DEFAULT_SHUFFLE_TRANSFERTO_ALLOWED = false; + private static final String TIMEOUT_HANDLER = "timeout"; /* the maximum number of files a single GET request can open simultaneously during shuffle @@ -249,8 +257,9 @@ public class ShuffleHandler extends AuxiliaryService { public static final int DEFAULT_SHUFFLE_MAX_SESSION_OPEN_FILES = 3; boolean connectionKeepAliveEnabled = false; - int connectionKeepAliveTimeOut; - int mapOutputMetaInfoCacheSize; + private int connectionKeepAliveTimeOut; + private int mapOutputMetaInfoCacheSize; + private Timer timer; @Metrics(about="Shuffle output metrics", context="mapred") static class ShuffleMetrics implements ChannelFutureListener { @@ -293,7 +302,15 @@ public class ShuffleHandler extends AuxiliaryService { int waitCount = this.reduceContext.getMapsToWait().decrementAndGet(); if (waitCount == 0) { metrics.operationComplete(future); - future.getChannel().close(); + // Let the idle timer handler close keep-alive connections + if (reduceContext.getKeepAlive()) { + ChannelPipeline pipeline = future.getChannel().getPipeline(); + TimeoutHandler timeoutHandler = + (TimeoutHandler)pipeline.get(TIMEOUT_HANDLER); + timeoutHandler.setEnabledTimeout(true); + } else { + future.getChannel().close(); + } } else { pipelineFact.getSHUFFLE().sendMap(reduceContext); } @@ -314,11 +331,12 @@ public class ShuffleHandler extends AuxiliaryService { private String user; private Map infoMap; private String jobId; + private final boolean keepAlive; public ReduceContext(List mapIds, int rId, ChannelHandlerContext context, String usr, Map mapOutputInfoMap, - String jobId) { + String jobId, boolean keepAlive) { this.mapIds = mapIds; this.reduceId = rId; @@ -339,6 +357,7 @@ public class ShuffleHandler extends AuxiliaryService { this.user = usr; this.infoMap = mapOutputInfoMap; this.jobId = jobId; + this.keepAlive = keepAlive; } public int getReduceId() { @@ -372,6 +391,10 @@ public class ShuffleHandler extends AuxiliaryService { public AtomicInteger getMapsToWait() { return mapsToWait; } + + public boolean getKeepAlive() { + return keepAlive; + } } ShuffleHandler(MetricsSystem ms) { @@ -508,8 +531,10 @@ public class ShuffleHandler extends AuxiliaryService { secretManager = new JobTokenSecretManager(); recoverState(conf); ServerBootstrap bootstrap = new ServerBootstrap(selector); + // Timer is shared across entire factory and must be released separately + timer = new HashedWheelTimer(); try { - pipelineFact = new HttpPipelineFactory(conf); + pipelineFact = new HttpPipelineFactory(conf, timer); } catch (Exception ex) { throw new RuntimeException(ex); } @@ -549,6 +574,10 @@ public class ShuffleHandler extends AuxiliaryService { if (pipelineFact != null) { pipelineFact.destroy(); } + if (timer != null) { + // Release this shared timer resource + timer.stop(); + } if (stateDb != null) { stateDb.close(); } @@ -755,12 +784,29 @@ public class ShuffleHandler extends AuxiliaryService { } } + static class TimeoutHandler extends IdleStateAwareChannelHandler { + + private boolean enabledTimeout; + + void setEnabledTimeout(boolean enabledTimeout) { + this.enabledTimeout = enabledTimeout; + } + + @Override + public void channelIdle(ChannelHandlerContext ctx, IdleStateEvent e) { + if (e.getState() == IdleState.WRITER_IDLE && enabledTimeout) { + e.getChannel().close(); + } + } + } + class HttpPipelineFactory implements ChannelPipelineFactory { final Shuffle SHUFFLE; private SSLFactory sslFactory; + private final ChannelHandler idleStateHandler; - public HttpPipelineFactory(Configuration conf) throws Exception { + public HttpPipelineFactory(Configuration conf, Timer timer) throws Exception { SHUFFLE = getShuffle(conf); if (conf.getBoolean(MRConfig.SHUFFLE_SSL_ENABLED_KEY, MRConfig.SHUFFLE_SSL_ENABLED_DEFAULT)) { @@ -768,6 +814,7 @@ public class ShuffleHandler extends AuxiliaryService { sslFactory = new SSLFactory(SSLFactory.Mode.SERVER, conf); sslFactory.init(); } + this.idleStateHandler = new IdleStateHandler(timer, 0, connectionKeepAliveTimeOut, 0); } public Shuffle getSHUFFLE() { @@ -791,6 +838,8 @@ public class ShuffleHandler extends AuxiliaryService { pipeline.addLast("encoder", new HttpResponseEncoder()); pipeline.addLast("chunking", new ChunkedWriteHandler()); pipeline.addLast("shuffle", SHUFFLE); + pipeline.addLast("idle", idleStateHandler); + pipeline.addLast(TIMEOUT_HANDLER, new TimeoutHandler()); return pipeline; // TODO factor security manager into pipeline // TODO factor out encode/decode to permit binary shuffle @@ -981,6 +1030,10 @@ public class ShuffleHandler extends AuxiliaryService { Map mapOutputInfoMap = new HashMap(); Channel ch = evt.getChannel(); + ChannelPipeline pipeline = ch.getPipeline(); + TimeoutHandler timeoutHandler = + (TimeoutHandler)pipeline.get(TIMEOUT_HANDLER); + timeoutHandler.setEnabledTimeout(false); String user = userRsrc.get(jobId); try { @@ -995,8 +1048,9 @@ public class ShuffleHandler extends AuxiliaryService { } ch.write(response); //Initialize one ReduceContext object per messageReceived call + boolean keepAlive = keepAliveParam || connectionKeepAliveEnabled; ReduceContext reduceContext = new ReduceContext(mapIds, reduceId, ctx, - user, mapOutputInfoMap, jobId); + user, mapOutputInfoMap, jobId, keepAlive); for (int i = 0; i < Math.min(maxSessionOpenFiles, mapIds.size()); i++) { ChannelFuture nextMap = sendMap(reduceContext); if(nextMap == null) { diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-shuffle/src/test/java/org/apache/hadoop/mapred/TestShuffleHandler.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-shuffle/src/test/java/org/apache/hadoop/mapred/TestShuffleHandler.java index 1e439374b5a..7fb2051cb35 100644 --- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-shuffle/src/test/java/org/apache/hadoop/mapred/TestShuffleHandler.java +++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-shuffle/src/test/java/org/apache/hadoop/mapred/TestShuffleHandler.java @@ -37,6 +37,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.net.HttpURLConnection; import java.net.URL; +import java.net.SocketAddress; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; @@ -79,6 +80,7 @@ import org.apache.hadoop.yarn.server.records.Version; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelFuture; import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.channel.ChannelPipeline; import org.jboss.netty.channel.socket.SocketChannel; import org.jboss.netty.channel.MessageEvent; import org.jboss.netty.channel.AbstractChannel; @@ -309,6 +311,15 @@ public class TestShuffleHandler { Assert.assertTrue("sendError called when client closed connection", failures.size() == 0); } + static class LastSocketAddress { + SocketAddress lastAddress; + void setAddress(SocketAddress lastAddress) { + this.lastAddress = lastAddress; + } + SocketAddress getSocketAddres() { + return lastAddress; + } + } @Test(timeout = 10000) public void testKeepAlive() throws Exception { @@ -318,6 +329,8 @@ public class TestShuffleHandler { conf.setBoolean(ShuffleHandler.SHUFFLE_CONNECTION_KEEP_ALIVE_ENABLED, true); // try setting to -ve keep alive timeout. conf.setInt(ShuffleHandler.SHUFFLE_CONNECTION_KEEP_ALIVE_TIME_OUT, -100); + final LastSocketAddress lastSocketAddress = new LastSocketAddress(); + ShuffleHandler shuffleHandler = new ShuffleHandler() { @Override protected Shuffle getShuffle(final Configuration conf) { @@ -363,6 +376,7 @@ public class TestShuffleHandler { protected ChannelFuture sendMapOutput(ChannelHandlerContext ctx, Channel ch, String user, String mapId, int reduce, MapOutputInfo info) throws IOException { + lastSocketAddress.setAddress(ch.getRemoteAddress()); HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK); // send a shuffle header and a lot of data down the channel @@ -422,6 +436,9 @@ public class TestShuffleHandler { Assert.assertEquals(HttpURLConnection.HTTP_OK, conn.getResponseCode()); ShuffleHeader header = new ShuffleHeader(); header.readFields(input); + byte[] buffer = new byte[1024]; + while (input.read(buffer) != -1) {} + SocketAddress firstAddress = lastSocketAddress.getSocketAddres(); input.close(); // For keepAlive via URL @@ -443,6 +460,14 @@ public class TestShuffleHandler { header = new ShuffleHeader(); header.readFields(input); input.close(); + SocketAddress secondAddress = lastSocketAddress.getSocketAddres(); + Assert.assertNotNull("Initial shuffle address should not be null", + firstAddress); + Assert.assertNotNull("Keep-Alive shuffle address should not be null", + secondAddress); + Assert.assertEquals("Initial shuffle address and keep-alive shuffle " + + "address should be the same", firstAddress, secondAddress); + } @Test(timeout = 10000) @@ -1058,14 +1083,20 @@ public class TestShuffleHandler { Mockito.mock(ChannelHandlerContext.class); final MessageEvent mockEvt = Mockito.mock(MessageEvent.class); final Channel mockCh = Mockito.mock(AbstractChannel.class); + final ChannelPipeline mockPipeline = Mockito.mock(ChannelPipeline.class); // Mock HttpRequest and ChannelFuture final HttpRequest mockHttpRequest = createMockHttpRequest(); final ChannelFuture mockFuture = createMockChannelFuture(mockCh, listenerList); + final ShuffleHandler.TimeoutHandler timerHandler = + new ShuffleHandler.TimeoutHandler(); // Mock Netty Channel Context and Channel behavior Mockito.doReturn(mockCh).when(mockCtx).getChannel(); + Mockito.when(mockCh.getPipeline()).thenReturn(mockPipeline); + Mockito.when(mockPipeline.get( + Mockito.any(String.class))).thenReturn(timerHandler); Mockito.when(mockCtx.getChannel()).thenReturn(mockCh); Mockito.doReturn(mockFuture).when(mockCh).write(Mockito.any(Object.class)); Mockito.when(mockCh.write(Object.class)).thenReturn(mockFuture); From 7297e82cb4ab1dff06da48acdb76e3d568f998f2 Mon Sep 17 00:00:00 2001 From: Chris Douglas Date: Thu, 30 Mar 2017 10:14:22 -0700 Subject: [PATCH 164/188] HADOOP-14250. Correct spelling of 'separate' and variants. Contributed by Doris Gu --- .../main/java/org/apache/hadoop/net/NodeBase.java | 2 +- .../java/org/apache/hadoop/util/StringUtils.java | 8 ++++---- .../apache/hadoop/fs/http/server/FSOperations.java | 2 +- .../hadoop/mapred/lib/FieldSelectionMapReduce.java | 2 +- .../hadoop/mapred/lib/KeyFieldBasedComparator.java | 2 +- .../org/apache/hadoop/mapreduce/MRJobConfig.java | 8 +++++++- .../lib/fieldsel/FieldSelectionHelper.java | 7 ++++++- .../lib/fieldsel/FieldSelectionMapper.java | 2 +- .../lib/fieldsel/FieldSelectionReducer.java | 2 +- .../lib/input/KeyValueLineRecordReader.java | 13 +++++++++---- .../mapreduce/lib/output/TextOutputFormat.java | 9 +++++++-- .../lib/partition/KeyFieldBasedComparator.java | 4 ++-- .../lib/partition/KeyFieldBasedPartitioner.java | 2 +- .../apache/hadoop/mapreduce/util/ConfigUtil.java | 8 ++++---- .../org/apache/hadoop/fs/slive/OperationOutput.java | 4 ++-- .../apache/hadoop/mapred/TestFieldSelection.java | 2 +- .../mapred/lib/TestKeyFieldBasedComparator.java | 4 ++-- .../lib/fieldsel/TestMRFieldSelection.java | 2 +- .../partition/TestMRKeyFieldBasedComparator.java | 4 ++-- .../apache/hadoop/yarn/conf/YarnConfiguration.java | 2 +- 20 files changed, 55 insertions(+), 34 deletions(-) diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/net/NodeBase.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/net/NodeBase.java index 7494adf74c8..9da9ca2948f 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/net/NodeBase.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/net/NodeBase.java @@ -45,7 +45,7 @@ public class NodeBase implements Node { /** Construct a node from its path * @param path - * a concatenation of this node's location, the path seperator, and its name + * a concatenation of this node's location, the path separator, and its name */ public NodeBase(String path) { path = normalize(path); diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/util/StringUtils.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/util/StringUtils.java index d2b572b707d..e773806c651 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/util/StringUtils.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/util/StringUtils.java @@ -372,8 +372,8 @@ public class StringUtils { /** * Returns an arraylist of strings. - * @param str the comma seperated string values - * @return the arraylist of the comma seperated string values + * @param str the comma separated string values + * @return the arraylist of the comma separated string values */ public static String[] getStrings(String str){ String delim = ","; @@ -384,7 +384,7 @@ public class StringUtils { * Returns an arraylist of strings. * @param str the string values * @param delim delimiter to separate the values - * @return the arraylist of the seperated string values + * @return the arraylist of the separated string values */ public static String[] getStrings(String str, String delim){ Collection values = getStringCollection(str, delim); @@ -396,7 +396,7 @@ public class StringUtils { /** * Returns a collection of strings. - * @param str comma seperated string values + * @param str comma separated string values * @return an ArrayList of string values */ public static Collection getStringCollection(String str){ diff --git a/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/fs/http/server/FSOperations.java b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/fs/http/server/FSOperations.java index eec6981da02..5ff42a46d31 100644 --- a/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/fs/http/server/FSOperations.java +++ b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/fs/http/server/FSOperations.java @@ -358,7 +358,7 @@ public class FSOperations { * Creates a Concat executor. * * @param path target path to concat to. - * @param sources comma seperated absolute paths to use as sources. + * @param sources comma separated absolute paths to use as sources. */ public FSConcat(String path, String[] sources) { this.sources = new Path[sources.length]; diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapred/lib/FieldSelectionMapReduce.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapred/lib/FieldSelectionMapReduce.java index 03335f90693..7a487850eee 100644 --- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapred/lib/FieldSelectionMapReduce.java +++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapred/lib/FieldSelectionMapReduce.java @@ -159,7 +159,7 @@ public class FieldSelectionMapReduce } public void configure(JobConf job) { - this.fieldSeparator = job.get(FieldSelectionHelper.DATA_FIELD_SEPERATOR, + this.fieldSeparator = job.get(FieldSelectionHelper.DATA_FIELD_SEPARATOR, "\t"); this.mapOutputKeyValueSpec = job.get( FieldSelectionHelper.MAP_OUTPUT_KEY_VALUE_SPEC, "0-:"); diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapred/lib/KeyFieldBasedComparator.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapred/lib/KeyFieldBasedComparator.java index c989d77b20f..7eda92a96df 100644 --- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapred/lib/KeyFieldBasedComparator.java +++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapred/lib/KeyFieldBasedComparator.java @@ -37,7 +37,7 @@ import org.apache.hadoop.mapreduce.JobContext; * of the field); if omitted from pos2, it defaults to 0 (the end of the * field). opts are ordering options (any of 'nr' as described above). * We assume that the fields in the key are separated by - * {@link JobContext#MAP_OUTPUT_KEY_FIELD_SEPERATOR} + * {@link JobContext#MAP_OUTPUT_KEY_FIELD_SEPARATOR} */ @InterfaceAudience.Public @InterfaceStability.Stable diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/MRJobConfig.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/MRJobConfig.java index 0a4d222f916..18bf1391fee 100644 --- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/MRJobConfig.java +++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/MRJobConfig.java @@ -318,7 +318,13 @@ public interface MRJobConfig { public static final String MAP_OUTPUT_VALUE_CLASS = "mapreduce.map.output.value.class"; - public static final String MAP_OUTPUT_KEY_FIELD_SEPERATOR = "mapreduce.map.output.key.field.separator"; + public static final String MAP_OUTPUT_KEY_FIELD_SEPARATOR = "mapreduce.map.output.key.field.separator"; + + /** + * @deprecated Use {@link #MAP_OUTPUT_KEY_FIELD_SEPARATOR} + */ + @Deprecated + public static final String MAP_OUTPUT_KEY_FIELD_SEPERATOR = MAP_OUTPUT_KEY_FIELD_SEPARATOR; public static final String MAP_LOG_LEVEL = "mapreduce.map.log.level"; diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/lib/fieldsel/FieldSelectionHelper.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/lib/fieldsel/FieldSelectionHelper.java index 6e22fe90442..5ee7e0f78b6 100644 --- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/lib/fieldsel/FieldSelectionHelper.java +++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/lib/fieldsel/FieldSelectionHelper.java @@ -60,8 +60,13 @@ import org.apache.hadoop.io.Text; public class FieldSelectionHelper { public static Text emptyText = new Text(""); - public static final String DATA_FIELD_SEPERATOR = + public static final String DATA_FIELD_SEPARATOR = "mapreduce.fieldsel.data.field.separator"; + /** + * @deprecated Use {@link #DATA_FIELD_SEPARATOR} + */ + @Deprecated + public static final String DATA_FIELD_SEPERATOR = DATA_FIELD_SEPARATOR; public static final String MAP_OUTPUT_KEY_VALUE_SPEC = "mapreduce.fieldsel.map.output.key.value.fields.spec"; public static final String REDUCE_OUTPUT_KEY_VALUE_SPEC = diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/lib/fieldsel/FieldSelectionMapper.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/lib/fieldsel/FieldSelectionMapper.java index 627e6626330..fbc1f606b04 100644 --- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/lib/fieldsel/FieldSelectionMapper.java +++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/lib/fieldsel/FieldSelectionMapper.java @@ -79,7 +79,7 @@ public class FieldSelectionMapper throws IOException, InterruptedException { Configuration conf = context.getConfiguration(); this.fieldSeparator = - conf.get(FieldSelectionHelper.DATA_FIELD_SEPERATOR, "\t"); + conf.get(FieldSelectionHelper.DATA_FIELD_SEPARATOR, "\t"); this.mapOutputKeyValueSpec = conf.get(FieldSelectionHelper.MAP_OUTPUT_KEY_VALUE_SPEC, "0-:"); try { diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/lib/fieldsel/FieldSelectionReducer.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/lib/fieldsel/FieldSelectionReducer.java index 8a6df33cbe2..5feeaf4692c 100644 --- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/lib/fieldsel/FieldSelectionReducer.java +++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/lib/fieldsel/FieldSelectionReducer.java @@ -77,7 +77,7 @@ public class FieldSelectionReducer Configuration conf = context.getConfiguration(); this.fieldSeparator = - conf.get(FieldSelectionHelper.DATA_FIELD_SEPERATOR, "\t"); + conf.get(FieldSelectionHelper.DATA_FIELD_SEPARATOR, "\t"); this.reduceOutputKeyValueSpec = conf.get(FieldSelectionHelper.REDUCE_OUTPUT_KEY_VALUE_SPEC, "0-:"); diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/lib/input/KeyValueLineRecordReader.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/lib/input/KeyValueLineRecordReader.java index 6d661211cf0..06ab6b3f76f 100644 --- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/lib/input/KeyValueLineRecordReader.java +++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/lib/input/KeyValueLineRecordReader.java @@ -37,9 +37,14 @@ import org.apache.hadoop.mapreduce.TaskAttemptContext; @InterfaceAudience.Public @InterfaceStability.Stable public class KeyValueLineRecordReader extends RecordReader { - public static final String KEY_VALUE_SEPERATOR = - "mapreduce.input.keyvaluelinerecordreader.key.value.separator"; - + public static final String KEY_VALUE_SEPARATOR = + "mapreduce.input.keyvaluelinerecordreader.key.value.separator"; + /** + * @deprecated Use {@link #KEY_VALUE_SEPARATOR} + */ + @Deprecated + public static final String KEY_VALUE_SEPERATOR = KEY_VALUE_SEPARATOR; + private final LineRecordReader lineRecordReader; private byte separator = (byte) '\t'; @@ -56,7 +61,7 @@ public class KeyValueLineRecordReader extends RecordReader { throws IOException { lineRecordReader = new LineRecordReader(); - String sepStr = conf.get(KEY_VALUE_SEPERATOR, "\t"); + String sepStr = conf.get(KEY_VALUE_SEPARATOR, "\t"); this.separator = (byte) sepStr.charAt(0); } diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/lib/output/TextOutputFormat.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/lib/output/TextOutputFormat.java index 2e49f68edab..8b27223df08 100644 --- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/lib/output/TextOutputFormat.java +++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/lib/output/TextOutputFormat.java @@ -42,7 +42,12 @@ import org.apache.hadoop.util.*; @InterfaceAudience.Public @InterfaceStability.Stable public class TextOutputFormat extends FileOutputFormat { - public static String SEPERATOR = "mapreduce.output.textoutputformat.separator"; + public static String SEPARATOR = "mapreduce.output.textoutputformat.separator"; + /** + * @deprecated Use {@link #SEPARATOR} + */ + @Deprecated + public static String SEPERATOR = SEPARATOR; protected static class LineRecordWriter extends RecordWriter { private static final byte[] NEWLINE = @@ -107,7 +112,7 @@ public class TextOutputFormat extends FileOutputFormat { ) throws IOException, InterruptedException { Configuration conf = job.getConfiguration(); boolean isCompressed = getCompressOutput(job); - String keyValueSeparator= conf.get(SEPERATOR, "\t"); + String keyValueSeparator= conf.get(SEPARATOR, "\t"); CompressionCodec codec = null; String extension = ""; if (isCompressed) { diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/lib/partition/KeyFieldBasedComparator.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/lib/partition/KeyFieldBasedComparator.java index 4b4d0e9e08e..416bc8c4764 100644 --- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/lib/partition/KeyFieldBasedComparator.java +++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/lib/partition/KeyFieldBasedComparator.java @@ -46,7 +46,7 @@ import org.apache.hadoop.mapreduce.lib.partition.KeyFieldHelper.KeyDescription; * of the field); if omitted from pos2, it defaults to 0 (the end of the * field). opts are ordering options (any of 'nr' as described above). * We assume that the fields in the key are separated by - * {@link JobContext#MAP_OUTPUT_KEY_FIELD_SEPERATOR}. + * {@link JobContext#MAP_OUTPUT_KEY_FIELD_SEPARATOR}. */ @InterfaceAudience.Public @InterfaceStability.Stable @@ -62,7 +62,7 @@ public class KeyFieldBasedComparator extends WritableComparator public void setConf(Configuration conf) { this.conf = conf; String option = conf.get(COMPARATOR_OPTIONS); - String keyFieldSeparator = conf.get(MRJobConfig.MAP_OUTPUT_KEY_FIELD_SEPERATOR,"\t"); + String keyFieldSeparator = conf.get(MRJobConfig.MAP_OUTPUT_KEY_FIELD_SEPARATOR,"\t"); keyFieldHelper.setKeyFieldSeparator(keyFieldSeparator); keyFieldHelper.parseOption(option); } diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/lib/partition/KeyFieldBasedPartitioner.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/lib/partition/KeyFieldBasedPartitioner.java index 44cb624c5a8..73f6a344ce4 100644 --- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/lib/partition/KeyFieldBasedPartitioner.java +++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/lib/partition/KeyFieldBasedPartitioner.java @@ -65,7 +65,7 @@ public class KeyFieldBasedPartitioner extends Partitioner this.conf = conf; keyFieldHelper = new KeyFieldHelper(); String keyFieldSeparator = - conf.get(MRJobConfig.MAP_OUTPUT_KEY_FIELD_SEPERATOR, "\t"); + conf.get(MRJobConfig.MAP_OUTPUT_KEY_FIELD_SEPARATOR, "\t"); keyFieldHelper.setKeyFieldSeparator(keyFieldSeparator); if (conf.get("num.key.fields.for.partition") != null) { LOG.warn("Using deprecated num.key.fields.for.partition. " + diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/util/ConfigUtil.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/util/ConfigUtil.java index d3f62e4c930..4b473792717 100644 --- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/util/ConfigUtil.java +++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/util/ConfigUtil.java @@ -290,7 +290,7 @@ public class ConfigUtil { new DeprecationDelta("mapred.mapoutput.value.class", MRJobConfig.MAP_OUTPUT_VALUE_CLASS), new DeprecationDelta("map.output.key.field.separator", - MRJobConfig.MAP_OUTPUT_KEY_FIELD_SEPERATOR), + MRJobConfig.MAP_OUTPUT_KEY_FIELD_SEPARATOR), new DeprecationDelta("mapred.map.child.log.level", MRJobConfig.MAP_LOG_LEVEL), new DeprecationDelta("mapred.inmem.merge.threshold", @@ -412,7 +412,7 @@ public class ConfigUtil { ControlledJob.CREATE_DIR), new DeprecationDelta("mapred.data.field.separator", org.apache.hadoop.mapreduce.lib.fieldsel. - FieldSelectionHelper.DATA_FIELD_SEPERATOR), + FieldSelectionHelper.DATA_FIELD_SEPARATOR), new DeprecationDelta("map.output.key.value.fields.spec", org.apache.hadoop.mapreduce.lib.fieldsel. FieldSelectionHelper.MAP_OUTPUT_KEY_VALUE_SPEC), @@ -427,7 +427,7 @@ public class ConfigUtil { CombineFileInputFormat.SPLIT_MINSIZE_PERRACK), new DeprecationDelta("key.value.separator.in.input.line", org.apache.hadoop.mapreduce.lib.input. - KeyValueLineRecordReader.KEY_VALUE_SEPERATOR), + KeyValueLineRecordReader.KEY_VALUE_SEPARATOR), new DeprecationDelta("mapred.linerecordreader.maxlength", org.apache.hadoop.mapreduce.lib.input. LineRecordReader.MAX_LINE_LENGTH), @@ -436,7 +436,7 @@ public class ConfigUtil { LazyOutputFormat.OUTPUT_FORMAT), new DeprecationDelta("mapred.textoutputformat.separator", org.apache.hadoop.mapreduce.lib.output. - TextOutputFormat.SEPERATOR), + TextOutputFormat.SEPARATOR), new DeprecationDelta("mapred.join.expr", org.apache.hadoop.mapreduce.lib.join. CompositeInputFormat.JOIN_EXPR), diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/java/org/apache/hadoop/fs/slive/OperationOutput.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/java/org/apache/hadoop/fs/slive/OperationOutput.java index bca5a1c777e..02584f19503 100644 --- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/java/org/apache/hadoop/fs/slive/OperationOutput.java +++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/java/org/apache/hadoop/fs/slive/OperationOutput.java @@ -65,7 +65,7 @@ class OperationOutput { int place = key.indexOf(TYPE_SEP); if (place == -1) { throw new IllegalArgumentException( - "Invalid key format - no type seperator - " + TYPE_SEP); + "Invalid key format - no type separator - " + TYPE_SEP); } try { dataType = OutputType.valueOf( @@ -78,7 +78,7 @@ class OperationOutput { place = key.indexOf(MEASUREMENT_SEP); if (place == -1) { throw new IllegalArgumentException( - "Invalid key format - no measurement seperator - " + MEASUREMENT_SEP); + "Invalid key format - no measurement separator - " + MEASUREMENT_SEP); } opType = key.substring(0, place); measurementType = key.substring(place + 1); diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/java/org/apache/hadoop/mapred/TestFieldSelection.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/java/org/apache/hadoop/mapred/TestFieldSelection.java index 868896815ef..4e14797a161 100644 --- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/java/org/apache/hadoop/mapred/TestFieldSelection.java +++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/java/org/apache/hadoop/mapred/TestFieldSelection.java @@ -77,7 +77,7 @@ private static NumberFormat idFormat = NumberFormat.getInstance(); job.setOutputFormat(TextOutputFormat.class); job.setNumReduceTasks(1); - job.set(FieldSelectionHelper.DATA_FIELD_SEPERATOR, "-"); + job.set(FieldSelectionHelper.DATA_FIELD_SEPARATOR, "-"); job.set(FieldSelectionHelper.MAP_OUTPUT_KEY_VALUE_SPEC, "6,5,1-3:0-"); job.set(FieldSelectionHelper.REDUCE_OUTPUT_KEY_VALUE_SPEC, ":4,3,2,1,0,0-"); diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/java/org/apache/hadoop/mapred/lib/TestKeyFieldBasedComparator.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/java/org/apache/hadoop/mapred/lib/TestKeyFieldBasedComparator.java index 35b3f243c7f..3f31546c789 100644 --- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/java/org/apache/hadoop/mapred/lib/TestKeyFieldBasedComparator.java +++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/java/org/apache/hadoop/mapred/lib/TestKeyFieldBasedComparator.java @@ -63,7 +63,7 @@ public class TestKeyFieldBasedComparator extends HadoopTestCase { super(HadoopTestCase.LOCAL_MR, HadoopTestCase.LOCAL_FS, 1, 1); conf = createJobConf(); localConf = createJobConf(); - localConf.set(JobContext.MAP_OUTPUT_KEY_FIELD_SEPERATOR, " "); + localConf.set(JobContext.MAP_OUTPUT_KEY_FIELD_SEPARATOR, " "); } public void configure(String keySpec, int expect) throws Exception { @@ -85,7 +85,7 @@ public class TestKeyFieldBasedComparator extends HadoopTestCase { conf.setOutputKeyComparatorClass(KeyFieldBasedComparator.class); conf.setKeyFieldComparatorOptions(keySpec); conf.setKeyFieldPartitionerOptions("-k1.1,1.1"); - conf.set(JobContext.MAP_OUTPUT_KEY_FIELD_SEPERATOR, " "); + conf.set(JobContext.MAP_OUTPUT_KEY_FIELD_SEPARATOR, " "); conf.setMapperClass(InverseMapper.class); conf.setReducerClass(IdentityReducer.class); if (!fs.mkdirs(testdir)) { diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/java/org/apache/hadoop/mapreduce/lib/fieldsel/TestMRFieldSelection.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/java/org/apache/hadoop/mapreduce/lib/fieldsel/TestMRFieldSelection.java index 6f9183ab21b..26cc328bc94 100644 --- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/java/org/apache/hadoop/mapreduce/lib/fieldsel/TestMRFieldSelection.java +++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/java/org/apache/hadoop/mapreduce/lib/fieldsel/TestMRFieldSelection.java @@ -57,7 +57,7 @@ private static NumberFormat idFormat = NumberFormat.getInstance(); StringBuffer expectedOutput = new StringBuffer(); constructInputOutputData(inputData, expectedOutput, numOfInputLines); - conf.set(FieldSelectionHelper.DATA_FIELD_SEPERATOR, "-"); + conf.set(FieldSelectionHelper.DATA_FIELD_SEPARATOR, "-"); conf.set(FieldSelectionHelper.MAP_OUTPUT_KEY_VALUE_SPEC, "6,5,1-3:0-"); conf.set( FieldSelectionHelper.REDUCE_OUTPUT_KEY_VALUE_SPEC, ":4,3,2,1,0,0-"); diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/java/org/apache/hadoop/mapreduce/lib/partition/TestMRKeyFieldBasedComparator.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/java/org/apache/hadoop/mapreduce/lib/partition/TestMRKeyFieldBasedComparator.java index 0d75d2fe9ea..889137c20e5 100644 --- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/java/org/apache/hadoop/mapreduce/lib/partition/TestMRKeyFieldBasedComparator.java +++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/java/org/apache/hadoop/mapreduce/lib/partition/TestMRKeyFieldBasedComparator.java @@ -50,7 +50,7 @@ public class TestMRKeyFieldBasedComparator extends HadoopTestCase { public TestMRKeyFieldBasedComparator() throws IOException { super(HadoopTestCase.LOCAL_MR, HadoopTestCase.LOCAL_FS, 1, 1); conf = createJobConf(); - conf.set(MRJobConfig.MAP_OUTPUT_KEY_FIELD_SEPERATOR, " "); + conf.set(MRJobConfig.MAP_OUTPUT_KEY_FIELD_SEPARATOR, " "); } private void testComparator(String keySpec, int expect) @@ -61,7 +61,7 @@ public class TestMRKeyFieldBasedComparator extends HadoopTestCase { conf.set("mapreduce.partition.keycomparator.options", keySpec); conf.set("mapreduce.partition.keypartitioner.options", "-k1.1,1.1"); - conf.set(MRJobConfig.MAP_OUTPUT_KEY_FIELD_SEPERATOR, " "); + conf.set(MRJobConfig.MAP_OUTPUT_KEY_FIELD_SEPARATOR, " "); Job job = MapReduceTestUtil.createJob(conf, inDir, outDir, 1, 1, line1 +"\n" + line2 + "\n"); diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java index f52e487524d..c2392499f70 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java @@ -1897,7 +1897,7 @@ public class YarnConfiguration extends Configuration { public static final float DEFAULT_TIMELINE_SERVICE_VERSION = 1.0f; /** - * Comma seperated list of names for UIs hosted in the timeline server + * Comma separated list of names for UIs hosted in the timeline server * (For pluggable UIs). */ public static final String TIMELINE_SERVICE_UI_NAMES = From c9b7ce92737f0a829408936ed9a361220d3e9680 Mon Sep 17 00:00:00 2001 From: Jason Lowe Date: Thu, 30 Mar 2017 12:54:11 -0500 Subject: [PATCH 165/188] MAPREDUCE-6836. exception thrown when accessing the job configuration web UI. Contributed by Haibo Chen --- .../org/apache/hadoop/mapreduce/v2/app/webapp/ConfBlock.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/main/java/org/apache/hadoop/mapreduce/v2/app/webapp/ConfBlock.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/main/java/org/apache/hadoop/mapreduce/v2/app/webapp/ConfBlock.java index 532c2bd4fa4..98a2ce1d0cc 100644 --- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/main/java/org/apache/hadoop/mapreduce/v2/app/webapp/ConfBlock.java +++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/main/java/org/apache/hadoop/mapreduce/v2/app/webapp/ConfBlock.java @@ -70,7 +70,7 @@ public class ConfBlock extends HtmlBlock { try { ConfInfo info = new ConfInfo(job); - html.div().a("/jobhistory/downloadconf/" + jid, confPath.toString()); + html.div().a("/jobhistory/downloadconf/" + jid, confPath.toString())._(); TBODY> tbody = html. // Tasks table table("#conf"). From 8c591b8d199325f49b5bba29240ca25610cf80a0 Mon Sep 17 00:00:00 2001 From: Wei-Chiu Chuang Date: Thu, 30 Mar 2017 11:16:05 -0700 Subject: [PATCH 166/188] HDFS-10974. Document replication factor for EC files. Contributed by Yiqun Lin. --- .../org/apache/hadoop/fs/shell/SetReplication.java | 13 +++++++------ .../src/site/markdown/FileSystemShell.md | 2 +- .../hadoop-common/src/test/resources/testConf.xml | 2 +- .../src/site/markdown/HDFSErasureCoding.md | 1 + .../hadoop-distcp/src/site/markdown/DistCp.md.vm | 2 +- 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/shell/SetReplication.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/shell/SetReplication.java index fab0349a360..2231c58f2b5 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/shell/SetReplication.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/shell/SetReplication.java @@ -41,12 +41,13 @@ class SetReplication extends FsCommand { public static final String NAME = "setrep"; public static final String USAGE = "[-R] [-w] ..."; public static final String DESCRIPTION = - "Set the replication level of a file. If is a directory " + - "then the command recursively changes the replication factor of " + - "all files under the directory tree rooted at .\n" + - "-w: It requests that the command waits for the replication " + - "to complete. This can potentially take a very long time.\n" + - "-R: It is accepted for backwards compatibility. It has no effect."; + "Set the replication level of a file. If is a directory " + + "then the command recursively changes the replication factor of " + + "all files under the directory tree rooted at . " + + "The EC files will be ignored here.\n" + + "-w: It requests that the command waits for the replication " + + "to complete. This can potentially take a very long time.\n" + + "-R: It is accepted for backwards compatibility. It has no effect."; protected short newRep = 0; protected List waitList = new LinkedList(); diff --git a/hadoop-common-project/hadoop-common/src/site/markdown/FileSystemShell.md b/hadoop-common-project/hadoop-common/src/site/markdown/FileSystemShell.md index 42fddc94fd6..5db96eb4485 100644 --- a/hadoop-common-project/hadoop-common/src/site/markdown/FileSystemShell.md +++ b/hadoop-common-project/hadoop-common/src/site/markdown/FileSystemShell.md @@ -647,7 +647,7 @@ setrep Usage: `hadoop fs -setrep [-R] [-w] ` -Changes the replication factor of a file. If *path* is a directory then the command recursively changes the replication factor of all files under the directory tree rooted at *path*. +Changes the replication factor of a file. If *path* is a directory then the command recursively changes the replication factor of all files under the directory tree rooted at *path*. The EC files will be ignored when executing this command. Options: diff --git a/hadoop-common-project/hadoop-common/src/test/resources/testConf.xml b/hadoop-common-project/hadoop-common/src/test/resources/testConf.xml index 112aea00029..6347aa0d3ea 100644 --- a/hadoop-common-project/hadoop-common/src/test/resources/testConf.xml +++ b/hadoop-common-project/hadoop-common/src/test/resources/testConf.xml @@ -778,7 +778,7 @@ RegexpComparator - ^\s*rooted at <path>\.( )* + ^\s*rooted at <path>\. The EC files will be ignored here\.( )* RegexpComparator diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/site/markdown/HDFSErasureCoding.md b/hadoop-hdfs-project/hadoop-hdfs/src/site/markdown/HDFSErasureCoding.md index 04acdce6ea0..f0c487df3d8 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/site/markdown/HDFSErasureCoding.md +++ b/hadoop-hdfs-project/hadoop-hdfs/src/site/markdown/HDFSErasureCoding.md @@ -23,6 +23,7 @@ Purpose However, for warm and cold datasets with relatively low I/O activities, additional block replicas are rarely accessed during normal operations, but still consume the same amount of resources as the first replica. Therefore, a natural improvement is to use Erasure Coding (EC) in place of replication, which provides the same level of fault-tolerance with much less storage space. In typical Erasure Coding (EC) setups, the storage overhead is no more than 50%. + Replication factor of an EC file is meaningless. It is always 1 and cannot be changed via -setrep command. Background ---------- diff --git a/hadoop-tools/hadoop-distcp/src/site/markdown/DistCp.md.vm b/hadoop-tools/hadoop-distcp/src/site/markdown/DistCp.md.vm index dbf0e8da669..41a6e94aeee 100644 --- a/hadoop-tools/hadoop-distcp/src/site/markdown/DistCp.md.vm +++ b/hadoop-tools/hadoop-distcp/src/site/markdown/DistCp.md.vm @@ -217,7 +217,7 @@ Command Line Options Flag | Description | Notes ----------------- | ------------------------------------ | -------- -`-p[rbugpcaxt]` | Preserve r: replication number b: block size u: user g: group p: permission c: checksum-type a: ACL x: XAttr t: timestamp | When `-update` is specified, status updates will **not** be synchronized unless the file sizes also differ (i.e. unless the file is re-created). If -pa is specified, DistCp preserves the permissions also because ACLs are a super-set of permissions. +`-p[rbugpcaxt]` | Preserve r: replication number b: block size u: user g: group p: permission c: checksum-type a: ACL x: XAttr t: timestamp | When `-update` is specified, status updates will **not** be synchronized unless the file sizes also differ (i.e. unless the file is re-created). If -pa is specified, DistCp preserves the permissions also because ACLs are a super-set of permissions. The option -pr is only valid if both source and target directory are not erasure coded. `-i` | Ignore failures | As explained in the Appendix, this option will keep more accurate statistics about the copy than the default case. It also preserves logs from failed copies, which can be valuable for debugging. Finally, a failing map will not cause the job to fail before all splits are attempted. `-log ` | Write logs to \ | DistCp keeps logs of each file it attempts to copy as map output. If a map fails, the log output will not be retained if it is re-executed. `-m ` | Maximum number of simultaneous copies | Specify the number of maps to copy data. Note that more maps may not necessarily improve throughput. From 1309c585fb9f632f7c649464ecbe358c5130b142 Mon Sep 17 00:00:00 2001 From: Jonathan Eagles Date: Thu, 30 Mar 2017 14:14:43 -0500 Subject: [PATCH 167/188] HADOOP-14216. Addendum to Improve Configuration XML Parsing Performance (jeagles) --- .../main/java/org/apache/hadoop/conf/Configuration.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/conf/Configuration.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/conf/Configuration.java index 36845e393bd..2ac52cb3949 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/conf/Configuration.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/conf/Configuration.java @@ -2756,9 +2756,6 @@ public class Configuration implements Iterable>, token.setLength(0); break; case "include": - if (!"xi".equals(reader.getPrefix())) { - break; - } // Determine href for xi:include String confInclude = null; attrCount = reader.getAttributeCount(); @@ -2795,9 +2792,6 @@ public class Configuration implements Iterable>, } break; case "fallback": - if (!"xi".equals(reader.getPrefix())) { - break; - } fallbackEntered = true; break; case "configuration": @@ -2833,9 +2827,6 @@ public class Configuration implements Iterable>, confSource.add(StringInterner.weakIntern(token.toString())); break; case "include": - if (!"xi".equals(reader.getPrefix())) { - break; - } if (fallbackAllowed && !fallbackEntered) { throw new IOException("Fetch fail on include with no " + "fallback while loading '" + name + "'"); From 7c2bc444b3d6750aafeed9b530c8e5b1bf95c1f4 Mon Sep 17 00:00:00 2001 From: Varun Saxena Date: Fri, 31 Mar 2017 02:02:57 +0530 Subject: [PATCH 168/188] YARN-6342. Make TimelineV2Client's drain timeout after stop configurable (Haibo Chen via Varun Saxena) --- .../org/apache/hadoop/yarn/conf/YarnConfiguration.java | 10 ++++++++++ .../yarn/client/api/impl/TimelineV2ClientImpl.java | 9 ++++++--- .../src/main/resources/yarn-default.xml | 9 +++++++++ 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java index c2392499f70..81cb8c6c453 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java @@ -2095,6 +2095,16 @@ public class YarnConfiguration extends Configuration { public static final int DEFAULT_NUMBER_OF_ASYNC_ENTITIES_TO_MERGE = 10; + + /** + * The time period for which timeline v2 client will wait for draining + * leftover entities after stop. + */ + public static final String TIMELINE_V2_CLIENT_DRAIN_TIME_MILLIS = + TIMELINE_SERVICE_CLIENT_PREFIX + "drain-entities.timeout.ms"; + public static final long DEFAULT_TIMELINE_V2_CLIENT_DRAIN_TIME_MILLIS + = 2000L; + // mark app-history related configs @Private as application history is going // to be integrated into the timeline service @Private diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/client/api/impl/TimelineV2ClientImpl.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/client/api/impl/TimelineV2ClientImpl.java index cef7e5f579e..5d88f708b15 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/client/api/impl/TimelineV2ClientImpl.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/client/api/impl/TimelineV2ClientImpl.java @@ -289,7 +289,7 @@ public class TimelineV2ClientImpl extends TimelineV2Client { * Time period for which the timelineclient will wait for draining after * stop. */ - private static final long DRAIN_TIME_PERIOD = 2000L; + private final long drainTimeoutPeriod; private int numberOfAsyncsToMerge; private final BlockingQueue timelineEntityQueue; @@ -300,6 +300,9 @@ public class TimelineV2ClientImpl extends TimelineV2Client { numberOfAsyncsToMerge = conf.getInt(YarnConfiguration.NUMBER_OF_ASYNC_ENTITIES_TO_MERGE, YarnConfiguration.DEFAULT_NUMBER_OF_ASYNC_ENTITIES_TO_MERGE); + drainTimeoutPeriod = conf.getLong( + YarnConfiguration.TIMELINE_V2_CLIENT_DRAIN_TIME_MILLIS, + YarnConfiguration.DEFAULT_TIMELINE_V2_CLIENT_DRAIN_TIME_MILLIS); } Runnable createRunnable() { @@ -330,7 +333,7 @@ public class TimelineV2ClientImpl extends TimelineV2Client { // Try to drain the remaining entities to be published @ the max for // 2 seconds long timeTillweDrain = - System.currentTimeMillis() + DRAIN_TIME_PERIOD; + System.currentTimeMillis() + drainTimeoutPeriod; while (!timelineEntityQueue.isEmpty()) { publishWithoutBlockingOnQueue(timelineEntityQueue.poll()); if (System.currentTimeMillis() > timeTillweDrain) { @@ -449,7 +452,7 @@ public class TimelineV2ClientImpl extends TimelineV2Client { LOG.info("Stopping TimelineClient."); executor.shutdownNow(); try { - executor.awaitTermination(DRAIN_TIME_PERIOD, TimeUnit.MILLISECONDS); + executor.awaitTermination(drainTimeoutPeriod, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { Thread.currentThread().interrupt(); e.printStackTrace(); diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml index 727e2c9fbdb..bdd4de52c0a 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml @@ -2101,6 +2101,15 @@ 1000 + + + The time period for which timeline v2 client will wait for draining + leftover entities after stop. + + yarn.timeline-service.client.drain-entities.timeout.ms + 2000 + + Enable timeline server to recover state after starting. If true, then yarn.timeline-service.state-store-class must be specified. From b58777a9c9a5b6f2e4bcfd2b3bede33f25f80dec Mon Sep 17 00:00:00 2001 From: Varun Saxena Date: Fri, 31 Mar 2017 02:17:20 +0530 Subject: [PATCH 169/188] YARN-6376. Exceptions caused by synchronous putEntities requests can be swallowed (Haibo Chen via Varun Saxena) --- .../timelineservice/collector/TimelineCollector.java | 10 ++++++++-- .../collector/TimelineCollectorManager.java | 7 ++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/collector/TimelineCollector.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/collector/TimelineCollector.java index 353066bd775..4c9e9f85d2a 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/collector/TimelineCollector.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/collector/TimelineCollector.java @@ -137,8 +137,14 @@ public abstract class TimelineCollector extends CompositeService { + callerUgi + ")"); } - TimelineWriteResponse response = writeTimelineEntities(entities); - flushBufferedTimelineEntities(); + TimelineWriteResponse response; + // synchronize on the writer object so that no other threads can + // flush the writer buffer concurrently and swallow any exception + // caused by the timeline enitites that are being put here. + synchronized (writer) { + response = writeTimelineEntities(entities); + flushBufferedTimelineEntities(); + } return response; } diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/collector/TimelineCollectorManager.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/collector/TimelineCollectorManager.java index 19896e82466..8ef9b43d517 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/collector/TimelineCollectorManager.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/collector/TimelineCollectorManager.java @@ -259,7 +259,12 @@ public class TimelineCollectorManager extends AbstractService { public void run() { try { - writer.flush(); + // synchronize on the writer object to avoid flushing timeline + // entities placed on the buffer by synchronous putEntities + // requests. + synchronized (writer) { + writer.flush(); + } } catch (Throwable th) { // we need to handle all exceptions or subsequent execution may be // suppressed From e5f0622a6f40706d360d45200c8f259c79046438 Mon Sep 17 00:00:00 2001 From: Mingliang Liu Date: Thu, 30 Mar 2017 15:44:06 -0700 Subject: [PATCH 170/188] HDFS-11592. Closing a file has a wasteful preconditions in NameNode. Contributed by Eric Badger --- .../org/apache/hadoop/hdfs/server/namenode/INodeFile.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) 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 2b0e0adea2a..3da6aa7c816 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 @@ -328,9 +328,11 @@ public class INodeFile extends INodeWithAdditionalFields for (int i = 0; i < blocks.length; i++) { final String err = checkBlockComplete(blocks, i, numCommittedAllowed, minReplication); - Preconditions.checkState(err == null, - "Unexpected block state: %s, file=%s (%s), blocks=%s (i=%s)", - err, this, getClass().getSimpleName(), Arrays.asList(blocks), i); + if(err != null) { + throw new IllegalStateException(String.format("Unexpected block state: " + + "%s, file=%s (%s), blocks=%s (i=%s)", err, this, + getClass().getSimpleName(), Arrays.asList(blocks), i)); + } } } From 064c8b25eca9bc825dc07a54d9147d65c9290a03 Mon Sep 17 00:00:00 2001 From: Yongjun Zhang Date: Thu, 30 Mar 2017 17:01:15 -0700 Subject: [PATCH 171/188] HADOOP-11794. Enable distcp to copy blocks in parallel. Contributed by Yongjun Zhang, Wei-Chiu Chuang, Xiao Chen. --- .../org/apache/hadoop/hdfs/DFSTestUtil.java | 22 +- .../org/apache/hadoop/tools/CopyListing.java | 37 +- .../hadoop/tools/CopyListingFileStatus.java | 87 ++++- .../java/org/apache/hadoop/tools/DistCp.java | 52 +++ .../hadoop/tools/DistCpOptionSwitch.java | 10 + .../apache/hadoop/tools/DistCpOptions.java | 22 +- .../apache/hadoop/tools/OptionsParser.java | 36 +- .../hadoop/tools/SimpleCopyListing.java | 83 ++-- .../hadoop/tools/mapred/CopyCommitter.java | 174 ++++++++- .../hadoop/tools/mapred/CopyMapper.java | 40 +- .../mapred/RetriableFileCopyCommand.java | 26 +- .../tools/mapred/UniformSizeInputFormat.java | 5 +- .../apache/hadoop/tools/util/DistCpUtils.java | 111 +++++- .../src/site/markdown/DistCp.md.vm | 1 + .../apache/hadoop/tools/TestDistCpSystem.java | 368 ++++++++++++++++-- .../hadoop/tools/TestOptionsParser.java | 2 +- .../tools/mapred/TestCopyCommitter.java | 5 +- 17 files changed, 971 insertions(+), 110 deletions(-) diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/DFSTestUtil.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/DFSTestUtil.java index 13291952a9e..9b782f32f2a 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/DFSTestUtil.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/DFSTestUtil.java @@ -862,7 +862,27 @@ public class DFSTestUtil { out.write(toAppend); } } - + + /** + * Append specified length of bytes to a given file, starting with new block. + * @param fs The file system + * @param p Path of the file to append + * @param length Length of bytes to append to the file + * @throws IOException + */ + public static void appendFileNewBlock(DistributedFileSystem fs, + Path p, int length) throws IOException { + assert fs.exists(p); + assert length >= 0; + byte[] toAppend = new byte[length]; + Random random = new Random(); + random.nextBytes(toAppend); + try (FSDataOutputStream out = fs.append(p, + EnumSet.of(CreateFlag.APPEND, CreateFlag.NEW_BLOCK), 4096, null)) { + out.write(toAppend); + } + } + /** * @return url content as string (UTF-8 encoding assumed) */ diff --git a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/CopyListing.java b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/CopyListing.java index 481aa61b0f1..9ebf9d29b3b 100644 --- a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/CopyListing.java +++ b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/CopyListing.java @@ -145,12 +145,22 @@ public abstract class CopyListing extends Configured { Configuration config = getConf(); FileSystem fs = pathToListFile.getFileSystem(config); - Path sortedList = DistCpUtils.sortListing(fs, config, pathToListFile); + final boolean splitLargeFile = options.splitLargeFile(); + + // When splitLargeFile is enabled, we don't randomize the copylist + // earlier, so we don't do the sorting here. For a file that has + // multiple entries due to split, we check here that their + // is continuous. + // + Path checkPath = splitLargeFile? + pathToListFile : DistCpUtils.sortListing(fs, config, pathToListFile); SequenceFile.Reader reader = new SequenceFile.Reader( - config, SequenceFile.Reader.file(sortedList)); + config, SequenceFile.Reader.file(checkPath)); try { Text lastKey = new Text("*"); //source relative path can never hold * + long lastChunkOffset = -1; + long lastChunkLength = -1; CopyListingFileStatus lastFileStatus = new CopyListingFileStatus(); Text currentKey = new Text(); @@ -161,8 +171,21 @@ public abstract class CopyListing extends Configured { if (currentKey.equals(lastKey)) { CopyListingFileStatus currentFileStatus = new CopyListingFileStatus(); reader.getCurrentValue(currentFileStatus); - throw new DuplicateFileException("File " + lastFileStatus.getPath() + " and " + - currentFileStatus.getPath() + " would cause duplicates. Aborting"); + if (!splitLargeFile) { + throw new DuplicateFileException("File " + lastFileStatus.getPath() + + " and " + currentFileStatus.getPath() + + " would cause duplicates. Aborting"); + } else { + if (lastChunkOffset + lastChunkLength != + currentFileStatus.getChunkOffset()) { + throw new InvalidInputException("File " + lastFileStatus.getPath() + + " " + lastChunkOffset + "," + lastChunkLength + + " and " + currentFileStatus.getPath() + + " " + currentFileStatus.getChunkOffset() + "," + + currentFileStatus.getChunkLength() + + " are not continuous. Aborting"); + } + } } reader.getCurrentValue(lastFileStatus); if (options.shouldPreserve(DistCpOptions.FileAttribute.ACL)) { @@ -181,8 +204,12 @@ public abstract class CopyListing extends Configured { xAttrSupportCheckFsSet.add(lastFsUri); } } - lastKey.set(currentKey); + lastKey.set(currentKey); + if (splitLargeFile) { + lastChunkOffset = lastFileStatus.getChunkOffset(); + lastChunkLength = lastFileStatus.getChunkLength(); + } if (options.shouldUseDiff() && LOG.isDebugEnabled()) { LOG.debug("Copy list entry " + idx + ": " + lastFileStatus.getPath().toUri().getPath()); diff --git a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/CopyListingFileStatus.java b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/CopyListingFileStatus.java index 00d4b325053..29c59ac1033 100644 --- a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/CopyListingFileStatus.java +++ b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/CopyListingFileStatus.java @@ -74,6 +74,14 @@ public final class CopyListingFileStatus implements Writable { private List aclEntries; private Map xAttrs; + // represents the offset and length of a file + // chunk in number of bytes. + // used when splitting a large file to chunks to copy in parallel. + // If a file is not large enough to split, chunkOffset would be 0 and + // chunkLength would be the length of the file. + private long chunkOffset = 0; + private long chunkLength = Long.MAX_VALUE; + /** * Default constructor. */ @@ -96,11 +104,32 @@ public final class CopyListingFileStatus implements Writable { fileStatus.getPath()); } + public CopyListingFileStatus(FileStatus fileStatus, + long chunkOffset, long chunkLength) { + this(fileStatus.getLen(), fileStatus.isDirectory(), + fileStatus.getReplication(), fileStatus.getBlockSize(), + fileStatus.getModificationTime(), fileStatus.getAccessTime(), + fileStatus.getPermission(), fileStatus.getOwner(), + fileStatus.getGroup(), + fileStatus.getPath()); + this.chunkOffset = chunkOffset; + this.chunkLength = chunkLength; + } + @SuppressWarnings("checkstyle:parameternumber") public CopyListingFileStatus(long length, boolean isdir, int blockReplication, long blocksize, long modificationTime, long accessTime, FsPermission permission, String owner, String group, Path path) { + this(length, isdir, blockReplication, blocksize, modificationTime, + accessTime, permission, owner, group, path, 0, Long.MAX_VALUE); + } + + @SuppressWarnings("checkstyle:parameternumber") + public CopyListingFileStatus(long length, boolean isdir, + int blockReplication, long blocksize, long modificationTime, + long accessTime, FsPermission permission, String owner, String group, + Path path, long chunkOffset, long chunkLength) { this.length = length; this.isdir = isdir; this.blockReplication = (short)blockReplication; @@ -117,6 +146,23 @@ public final class CopyListingFileStatus implements Writable { this.owner = (owner == null) ? "" : owner; this.group = (group == null) ? "" : group; this.path = path; + this.chunkOffset = chunkOffset; + this.chunkLength = chunkLength; + } + + public CopyListingFileStatus(CopyListingFileStatus other) { + this.length = other.length; + this.isdir = other.isdir; + this.blockReplication = other.blockReplication; + this.blocksize = other.blocksize; + this.modificationTime = other.modificationTime; + this.accessTime = other.accessTime; + this.permission = other.permission; + this.owner = other.owner; + this.group = other.group; + this.path = new Path(other.path.toUri()); + this.chunkOffset = other.chunkOffset; + this.chunkLength = other.chunkLength; } public Path getPath() { @@ -200,6 +246,31 @@ public final class CopyListingFileStatus implements Writable { this.xAttrs = xAttrs; } + public long getChunkOffset() { + return chunkOffset; + } + + public void setChunkOffset(long offset) { + this.chunkOffset = offset; + } + + public long getChunkLength() { + return chunkLength; + } + + public void setChunkLength(long chunkLength) { + this.chunkLength = chunkLength; + } + + public boolean isSplit() { + return getChunkLength() != Long.MAX_VALUE && + getChunkLength() != getLen(); + } + + public long getSizeToCopy() { + return isSplit()? getChunkLength() : getLen(); + } + @Override public void write(DataOutput out) throws IOException { Text.writeString(out, getPath().toString(), Text.DEFAULT_MAX_LEN); @@ -244,6 +315,9 @@ public final class CopyListingFileStatus implements Writable { } else { out.writeInt(NO_XATTRS); } + + out.writeLong(chunkOffset); + out.writeLong(chunkLength); } @Override @@ -292,6 +366,9 @@ public final class CopyListingFileStatus implements Writable { } else { xAttrs = null; } + + chunkOffset = in.readLong(); + chunkLength = in.readLong(); } @Override @@ -317,8 +394,14 @@ public final class CopyListingFileStatus implements Writable { public String toString() { StringBuilder sb = new StringBuilder(super.toString()); sb.append('{'); - sb.append("aclEntries = " + aclEntries); - sb.append(", xAttrs = " + xAttrs); + sb.append(this.getPath().toString()); + sb.append(" length = ").append(this.getLen()); + sb.append(" aclEntries = ").append(aclEntries); + sb.append(", xAttrs = ").append(xAttrs); + if (isSplit()) { + sb.append(", chunkOffset = ").append(this.getChunkOffset()); + sb.append(", chunkLength = ").append(this.getChunkLength()); + } sb.append('}'); return sb.toString(); } diff --git a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCp.java b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCp.java index ab58e9c66d8..8c2fa24a15c 100644 --- a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCp.java +++ b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCp.java @@ -35,6 +35,7 @@ import org.apache.hadoop.mapreduce.Cluster; import org.apache.hadoop.mapreduce.Job; import org.apache.hadoop.mapreduce.JobContext; import org.apache.hadoop.mapreduce.JobSubmissionFiles; +import org.apache.hadoop.tools.DistCpOptions.FileAttribute; import org.apache.hadoop.tools.CopyListing.*; import org.apache.hadoop.tools.mapred.CopyMapper; import org.apache.hadoop.tools.mapred.CopyOutputFormat; @@ -134,6 +135,7 @@ public class DistCp extends Configured implements Tool { try { inputOptions = (OptionsParser.parse(argv)); + setOptionsForSplitLargeFile(); setTargetPathExists(); LOG.info("Input Options: " + inputOptions); } catch (Throwable e) { @@ -235,6 +237,56 @@ public class DistCp extends Configured implements Tool { getConf().setBoolean(DistCpConstants.CONF_LABEL_TARGET_PATH_EXISTS, targetExists); } + + /** + * Check if concat is supported by fs. + * Throws UnsupportedOperationException if not. + */ + private void checkConcatSupport(FileSystem fs) { + try { + Path[] src = null; + Path tgt = null; + fs.concat(tgt, src); + } catch (UnsupportedOperationException use) { + throw new UnsupportedOperationException( + DistCpOptionSwitch.BLOCKS_PER_CHUNK.getSwitch() + + " is not supported since the target file system doesn't" + + " support concat.", use); + } catch (Exception e) { + // Ignore other exception + } + } + + /** + * Set up needed options for splitting large files. + */ + private void setOptionsForSplitLargeFile() throws IOException { + if (!inputOptions.splitLargeFile()) { + return; + } + Path target = inputOptions.getTargetPath(); + FileSystem targetFS = target.getFileSystem(getConf()); + checkConcatSupport(targetFS); + + LOG.info("Enabling preserving blocksize since " + + DistCpOptionSwitch.BLOCKS_PER_CHUNK.getSwitch() + " is passed."); + inputOptions.preserve(FileAttribute.BLOCKSIZE); + + LOG.info("Set " + + DistCpOptionSwitch.APPEND.getSwitch() + + " to false since " + DistCpOptionSwitch.BLOCKS_PER_CHUNK.getSwitch() + + " is passed."); + inputOptions.setAppend(false); + + LOG.info("Set " + + DistCpConstants.CONF_LABEL_SIMPLE_LISTING_RANDOMIZE_FILES + + " to false since " + DistCpOptionSwitch.BLOCKS_PER_CHUNK.getSwitch() + + " is passed."); + getConf().setBoolean( + DistCpConstants.CONF_LABEL_SIMPLE_LISTING_RANDOMIZE_FILES, false); + } + + /** * Create Job object for submitting it, with all the configuration * diff --git a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCpOptionSwitch.java b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCpOptionSwitch.java index fb47d769233..ced9b540c89 100644 --- a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCpOptionSwitch.java +++ b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCpOptionSwitch.java @@ -169,6 +169,16 @@ public enum DistCpOptionSwitch { new Option("sizelimit", true, "(Deprecated!) Limit number of files " + "copied to <= n bytes")), + BLOCKS_PER_CHUNK("", + new Option("blocksperchunk", true, "If set to a positive value, files" + + "with more blocks than this value will be split into chunks of " + + " blocks to be transferred in parallel, and " + + "reassembled on the destination. By default, is " + + "0 and the files will be transmitted in their entirety without " + + "splitting. This switch is only applicable when the source file " + + "system implements getBlockLocations method and the target file " + + "system implements concat method")), + /** * Specify bandwidth per map in MB, accepts bandwidth as a fraction */ diff --git a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCpOptions.java b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCpOptions.java index 8c37ff30ae0..9822d83dbd0 100644 --- a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCpOptions.java +++ b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCpOptions.java @@ -97,7 +97,11 @@ public class DistCpOptions { // targetPathExist is a derived field, it's initialized in the // beginning of distcp. private boolean targetPathExists = true; - + + // Size of chunk in number of blocks when splitting large file into chunks + // to copy in parallel. Default is 0 and file are not splitted. + private int blocksPerChunk = 0; + public static enum FileAttribute{ REPLICATION, BLOCKSIZE, USER, GROUP, PERMISSION, CHECKSUMTYPE, ACL, XATTR, TIMES; @@ -166,6 +170,7 @@ public class DistCpOptions { this.targetPath = that.getTargetPath(); this.targetPathExists = that.getTargetPathExists(); this.filtersFile = that.getFiltersFile(); + this.blocksPerChunk = that.blocksPerChunk; } } @@ -578,6 +583,18 @@ public class DistCpOptions { this.filtersFile = filtersFilename; } + public final void setBlocksPerChunk(int csize) { + this.blocksPerChunk = csize; + } + + public final int getBlocksPerChunk() { + return blocksPerChunk; + } + + public final boolean splitLargeFile() { + return blocksPerChunk > 0; + } + void validate() { if ((useDiff || useRdiff) && deleteMissing) { // -delete and -diff/-rdiff are mutually exclusive. For backward @@ -669,6 +686,8 @@ public class DistCpOptions { DistCpOptionSwitch.addToConf(conf, DistCpOptionSwitch.FILTERS, filtersFile); } + DistCpOptionSwitch.addToConf(conf, DistCpOptionSwitch.BLOCKS_PER_CHUNK, + String.valueOf(blocksPerChunk)); } /** @@ -704,6 +723,7 @@ public class DistCpOptions { ", targetPath=" + targetPath + ", targetPathExists=" + targetPathExists + ", filtersFile='" + filtersFile + '\'' + + ", blocksPerChunk=" + blocksPerChunk + '}'; } diff --git a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/OptionsParser.java b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/OptionsParser.java index d0f82ca76b5..8881264d281 100644 --- a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/OptionsParser.java +++ b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/OptionsParser.java @@ -172,11 +172,44 @@ public class OptionsParser { DistCpOptionSwitch.FILTERS.getSwitch())); } + parseBlocksPerChunk(command, option); + option.validate(); return option; } + + /** + * A helper method to parse chunk size in number of blocks. + * Used when breaking large file into chunks to copy in parallel. + * + * @param command command line arguments + */ + private static void parseBlocksPerChunk(CommandLine command, + DistCpOptions option) { + boolean hasOption = + command.hasOption(DistCpOptionSwitch.BLOCKS_PER_CHUNK.getSwitch()); + LOG.info("parseChunkSize: " + + DistCpOptionSwitch.BLOCKS_PER_CHUNK.getSwitch() + " " + hasOption); + if (hasOption) { + String chunkSizeString = getVal(command, + DistCpOptionSwitch.BLOCKS_PER_CHUNK.getSwitch().trim()); + try { + int csize = Integer.parseInt(chunkSizeString); + if (csize < 0) { + csize = 0; + } + LOG.info("Set distcp blocksPerChunk to " + csize); + option.setBlocksPerChunk(csize); + } + catch (NumberFormatException e) { + throw new IllegalArgumentException("blocksPerChunk is invalid: " + + chunkSizeString, e); + } + } + } + /** * parseSizeLimit is a helper method for parsing the deprecated * argument SIZE_LIMIT. @@ -211,8 +244,7 @@ public class OptionsParser { DistCpOptionSwitch.FILE_LIMIT.getSwitch().trim()); try { Integer.parseInt(fileLimitString); - } - catch (NumberFormatException e) { + } catch (NumberFormatException e) { throw new IllegalArgumentException("File-limit is invalid: " + fileLimitString, e); } diff --git a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/SimpleCopyListing.java b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/SimpleCopyListing.java index 105e4f2fe17..af913474550 100644 --- a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/SimpleCopyListing.java +++ b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/SimpleCopyListing.java @@ -19,6 +19,7 @@ package org.apache.hadoop.tools; import com.google.common.collect.Lists; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.fs.Path; @@ -47,6 +48,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Random; +import java.util.LinkedList; import static org.apache.hadoop.tools.DistCpConstants .HDFS_RESERVED_RAW_DIRECTORY_NAME; @@ -240,10 +242,10 @@ public class SimpleCopyListing extends CopyListing { final boolean preserveAcls = options.shouldPreserve(FileAttribute.ACL); final boolean preserveXAttrs = options.shouldPreserve(FileAttribute.XATTR); final boolean preserveRawXAttrs = options.shouldPreserveRawXattrs(); - CopyListingFileStatus fileCopyListingStatus = + LinkedList fileCopyListingStatus = DistCpUtils.toCopyListingFileStatus(sourceFS, fileStatus, - preserveAcls, preserveXAttrs, preserveRawXAttrs); - + preserveAcls, preserveXAttrs, preserveRawXAttrs, + options.getBlocksPerChunk()); writeToFileListingRoot(fileListWriter, fileCopyListingStatus, sourceRoot, options); } @@ -348,9 +350,10 @@ public class SimpleCopyListing extends CopyListing { FileStatus[] sourceFiles = sourceFS.listStatus(path); boolean explore = (sourceFiles != null && sourceFiles.length > 0); if (!explore || rootStatus.isDirectory()) { - CopyListingFileStatus rootCopyListingStatus = - DistCpUtils.toCopyListingFileStatus(sourceFS, rootStatus, - preserveAcls, preserveXAttrs, preserveRawXAttrs); + LinkedList rootCopyListingStatus = + DistCpUtils.toCopyListingFileStatus(sourceFS, rootStatus, + preserveAcls, preserveXAttrs, preserveRawXAttrs, + options.getBlocksPerChunk()); writeToFileListingRoot(fileListWriter, rootCopyListingStatus, sourcePathRoot, options); } @@ -360,20 +363,20 @@ public class SimpleCopyListing extends CopyListing { if (LOG.isDebugEnabled()) { LOG.debug("Recording source-path: " + sourceStatus.getPath() + " for copy."); } - CopyListingFileStatus sourceCopyListingStatus = - DistCpUtils.toCopyListingFileStatus(sourceFS, sourceStatus, - preserveAcls && sourceStatus.isDirectory(), - preserveXAttrs && sourceStatus.isDirectory(), - preserveRawXAttrs && sourceStatus.isDirectory()); - if (randomizeFileListing) { - addToFileListing(statusList, - new FileStatusInfo(sourceCopyListingStatus, sourcePathRoot), - fileListWriter); - } else { - writeToFileListing(fileListWriter, sourceCopyListingStatus, - sourcePathRoot); + LinkedList sourceCopyListingStatus = + DistCpUtils.toCopyListingFileStatus(sourceFS, sourceStatus, + preserveAcls && sourceStatus.isDirectory(), + preserveXAttrs && sourceStatus.isDirectory(), + preserveRawXAttrs && sourceStatus.isDirectory(), + options.getBlocksPerChunk()); + for (CopyListingFileStatus fs : sourceCopyListingStatus) { + if (randomizeFileListing) { + addToFileListing(statusList, + new FileStatusInfo(fs, sourcePathRoot), fileListWriter); + } else { + writeToFileListing(fileListWriter, fs, sourcePathRoot); + } } - if (sourceStatus.isDirectory()) { if (LOG.isDebugEnabled()) { LOG.debug("Adding source dir for traverse: " + sourceStatus.getPath()); @@ -641,18 +644,20 @@ public class SimpleCopyListing extends CopyListing { LOG.debug("Recording source-path: " + child.getPath() + " for copy."); } if (workResult.getSuccess()) { - CopyListingFileStatus childCopyListingStatus = + LinkedList childCopyListingStatus = DistCpUtils.toCopyListingFileStatus(sourceFS, child, preserveAcls && child.isDirectory(), preserveXAttrs && child.isDirectory(), - preserveRawXattrs && child.isDirectory()); - if (randomizeFileListing) { - addToFileListing(fileStatuses, - new FileStatusInfo(childCopyListingStatus, sourcePathRoot), - fileListWriter); - } else { - writeToFileListing(fileListWriter, childCopyListingStatus, - sourcePathRoot); + preserveRawXattrs && child.isDirectory(), + options.getBlocksPerChunk()); + + for (CopyListingFileStatus fs : childCopyListingStatus) { + if (randomizeFileListing) { + addToFileListing(fileStatuses, + new FileStatusInfo(fs, sourcePathRoot), fileListWriter); + } else { + writeToFileListing(fileListWriter, fs, sourcePathRoot); + } } } if (retry < maxRetries) { @@ -675,19 +680,21 @@ public class SimpleCopyListing extends CopyListing { } private void writeToFileListingRoot(SequenceFile.Writer fileListWriter, - CopyListingFileStatus fileStatus, Path sourcePathRoot, + LinkedList fileStatus, Path sourcePathRoot, DistCpOptions options) throws IOException { boolean syncOrOverwrite = options.shouldSyncFolder() || options.shouldOverwrite(); - if (fileStatus.getPath().equals(sourcePathRoot) && - fileStatus.isDirectory() && syncOrOverwrite) { - // Skip the root-paths when syncOrOverwrite - if (LOG.isDebugEnabled()) { - LOG.debug("Skip " + fileStatus.getPath()); - } - return; + for (CopyListingFileStatus fs : fileStatus) { + if (fs.getPath().equals(sourcePathRoot) && + fs.isDirectory() && syncOrOverwrite) { + // Skip the root-paths when syncOrOverwrite + if (LOG.isDebugEnabled()) { + LOG.debug("Skip " + fs.getPath()); + } + return; + } + writeToFileListing(fileListWriter, fs, sourcePathRoot); } - writeToFileListing(fileListWriter, fileStatus, sourcePathRoot); } private void writeToFileListing(SequenceFile.Writer fileListWriter, @@ -707,7 +714,7 @@ public class SimpleCopyListing extends CopyListing { fileListWriter.sync(); if (!fileStatus.isDirectory()) { - totalBytesToCopy += fileStatus.getLen(); + totalBytesToCopy += fileStatus.getSizeToCopy(); } else { totalDirs++; } diff --git a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/CopyCommitter.java b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/CopyCommitter.java index 75cefb488ae..6ddaab99c3d 100644 --- a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/CopyCommitter.java +++ b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/CopyCommitter.java @@ -27,6 +27,7 @@ import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.IOUtils; import org.apache.hadoop.io.SequenceFile; import org.apache.hadoop.io.Text; +import org.apache.hadoop.ipc.RemoteException; import org.apache.hadoop.mapreduce.JobContext; import org.apache.hadoop.mapreduce.JobStatus; import org.apache.hadoop.mapreduce.TaskAttemptContext; @@ -34,14 +35,17 @@ import org.apache.hadoop.mapreduce.lib.output.FileOutputCommitter; import org.apache.hadoop.tools.CopyListing; import org.apache.hadoop.tools.CopyListingFileStatus; import org.apache.hadoop.tools.DistCpConstants; +import org.apache.hadoop.tools.DistCpOptionSwitch; import org.apache.hadoop.tools.DistCpOptions; import org.apache.hadoop.tools.DistCpOptions.FileAttribute; import org.apache.hadoop.tools.GlobbedCopyListing; import org.apache.hadoop.tools.util.DistCpUtils; +import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; import java.util.EnumSet; +import java.util.LinkedList; import java.util.List; /** @@ -63,7 +67,8 @@ public class CopyCommitter extends FileOutputCommitter { private boolean syncFolder = false; private boolean overwrite = false; private boolean targetPathExists = true; - + private boolean ignoreFailures = false; + /** * Create a output committer * @@ -82,8 +87,13 @@ public class CopyCommitter extends FileOutputCommitter { Configuration conf = jobContext.getConfiguration(); syncFolder = conf.getBoolean(DistCpConstants.CONF_LABEL_SYNC_FOLDERS, false); overwrite = conf.getBoolean(DistCpConstants.CONF_LABEL_OVERWRITE, false); - targetPathExists = conf.getBoolean(DistCpConstants.CONF_LABEL_TARGET_PATH_EXISTS, true); - + targetPathExists = conf.getBoolean( + DistCpConstants.CONF_LABEL_TARGET_PATH_EXISTS, true); + ignoreFailures = conf.getBoolean( + DistCpOptionSwitch.IGNORE_FAILURES.getConfigLabel(), false); + + concatFileChunks(conf); + super.commitJob(jobContext); cleanupTempFiles(jobContext); @@ -169,9 +179,112 @@ public class CopyCommitter extends FileOutputCommitter { } } + private boolean isFileNotFoundException(IOException e) { + if (e instanceof FileNotFoundException) { + return true; + } + + if (e instanceof RemoteException) { + return ((RemoteException)e).unwrapRemoteException() + instanceof FileNotFoundException; + } + + return false; + } + + /** + * Concat chunk files for the same file into one. + * Iterate through copy listing, identify chunk files for the same file, + * concat them into one. + */ + private void concatFileChunks(Configuration conf) throws IOException { + + LOG.info("concat file chunks ..."); + + String spath = conf.get(DistCpConstants.CONF_LABEL_LISTING_FILE_PATH); + if (spath == null || spath.isEmpty()) { + return; + } + Path sourceListing = new Path(spath); + SequenceFile.Reader sourceReader = new SequenceFile.Reader(conf, + SequenceFile.Reader.file(sourceListing)); + Path targetRoot = + new Path(conf.get(DistCpConstants.CONF_LABEL_TARGET_WORK_PATH)); + + try { + CopyListingFileStatus srcFileStatus = new CopyListingFileStatus(); + Text srcRelPath = new Text(); + CopyListingFileStatus lastFileStatus = null; + LinkedList allChunkPaths = new LinkedList(); + + // Iterate over every source path that was copied. + while (sourceReader.next(srcRelPath, srcFileStatus)) { + if (srcFileStatus.isDirectory()) { + continue; + } + Path targetFile = new Path(targetRoot.toString() + "/" + srcRelPath); + Path targetFileChunkPath = + DistCpUtils.getSplitChunkPath(targetFile, srcFileStatus); + if (LOG.isDebugEnabled()) { + LOG.debug(" add " + targetFileChunkPath + " to concat."); + } + allChunkPaths.add(targetFileChunkPath); + if (srcFileStatus.getChunkOffset() + srcFileStatus.getChunkLength() + == srcFileStatus.getLen()) { + // This is the last chunk of the splits, consolidate allChunkPaths + try { + concatFileChunks(conf, targetFile, allChunkPaths); + } catch (IOException e) { + // If the concat failed because a chunk file doesn't exist, + // then we assume that the CopyMapper has skipped copying this + // file, and we ignore the exception here. + // If a chunk file should have been created but it was not, then + // the CopyMapper would have failed. + if (!isFileNotFoundException(e)) { + String emsg = "Failed to concat chunk files for " + targetFile; + if (!ignoreFailures) { + throw new IOException(emsg, e); + } else { + LOG.warn(emsg, e); + } + } + } + allChunkPaths.clear(); + lastFileStatus = null; + } else { + if (lastFileStatus == null) { + lastFileStatus = new CopyListingFileStatus(srcFileStatus); + } else { + // Two neighboring chunks have to be consecutive ones for the same + // file, for them to be merged + if (!srcFileStatus.getPath().equals(lastFileStatus.getPath()) || + srcFileStatus.getChunkOffset() != + (lastFileStatus.getChunkOffset() + + lastFileStatus.getChunkLength())) { + String emsg = "Inconsistent sequence file: current " + + "chunk file " + srcFileStatus + " doesnt match prior " + + "entry " + lastFileStatus; + if (!ignoreFailures) { + throw new IOException(emsg); + } else { + LOG.warn(emsg + ", skipping concat this set."); + } + } else { + lastFileStatus.setChunkOffset(srcFileStatus.getChunkOffset()); + lastFileStatus.setChunkLength(srcFileStatus.getChunkLength()); + } + } + } + } + } finally { + IOUtils.closeStream(sourceReader); + } + } + // This method changes the target-directories' file-attributes (owner, // user/group permissions, etc.) based on the corresponding source directories. - private void preserveFileAttributesForDirectories(Configuration conf) throws IOException { + private void preserveFileAttributesForDirectories(Configuration conf) + throws IOException { String attrSymbols = conf.get(DistCpConstants.CONF_LABEL_PRESERVE_STATUS); final boolean syncOrOverwrite = syncFolder || overwrite; @@ -325,4 +438,57 @@ public class CopyCommitter extends FileOutputCommitter { ", Unable to move to " + finalDir); } } + + /** + * Concat the passed chunk files into one and rename it the targetFile. + */ + private void concatFileChunks(Configuration conf, Path targetFile, + LinkedList allChunkPaths) throws IOException { + if (allChunkPaths.size() == 1) { + return; + } + if (LOG.isDebugEnabled()) { + LOG.debug("concat " + targetFile + " allChunkSize+ " + + allChunkPaths.size()); + } + FileSystem dstfs = targetFile.getFileSystem(conf); + + Path firstChunkFile = allChunkPaths.removeFirst(); + Path[] restChunkFiles = new Path[allChunkPaths.size()]; + allChunkPaths.toArray(restChunkFiles); + if (LOG.isDebugEnabled()) { + LOG.debug("concat: firstchunk: " + dstfs.getFileStatus(firstChunkFile)); + int i = 0; + for (Path f : restChunkFiles) { + LOG.debug("concat: other chunk: " + i + ": " + dstfs.getFileStatus(f)); + ++i; + } + } + dstfs.concat(firstChunkFile, restChunkFiles); + if (LOG.isDebugEnabled()) { + LOG.debug("concat: result: " + dstfs.getFileStatus(firstChunkFile)); + } + rename(dstfs, firstChunkFile, targetFile); + } + + /** + * Rename tmp to dst on destFileSys. + * @param destFileSys the file ssystem + * @param tmp the source path + * @param dst the destination path + * @throws IOException if renaming failed + */ + private static void rename(FileSystem destFileSys, Path tmp, Path dst) + throws IOException { + try { + if (destFileSys.exists(dst)) { + destFileSys.delete(dst, true); + } + destFileSys.rename(tmp, dst); + } catch (IOException ioe) { + throw new IOException("Fail to rename tmp file (=" + tmp + + ") to destination file (=" + dst + ")", ioe); + } + } + } diff --git a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/CopyMapper.java b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/CopyMapper.java index e1873f17e41..d6b3ba817b0 100644 --- a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/CopyMapper.java +++ b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/CopyMapper.java @@ -156,10 +156,12 @@ public class CopyMapper extends Mapper sourceFS = sourcePath.getFileSystem(conf); final boolean preserveXAttrs = fileAttributes.contains(FileAttribute.XATTR); - sourceCurrStatus = DistCpUtils.toCopyListingFileStatus(sourceFS, - sourceFS.getFileStatus(sourcePath), - fileAttributes.contains(FileAttribute.ACL), - preserveXAttrs, preserveRawXattrs); + sourceCurrStatus = DistCpUtils.toCopyListingFileStatusHelper(sourceFS, + sourceFS.getFileStatus(sourcePath), + fileAttributes.contains(FileAttribute.ACL), + preserveXAttrs, preserveRawXattrs, + sourceFileStatus.getChunkOffset(), + sourceFileStatus.getChunkLength()); } catch (FileNotFoundException e) { throw new IOException(new RetriableFileCopyCommand.CopyReadException(e)); } @@ -173,7 +175,8 @@ public class CopyMapper extends Mapper LOG.debug("Path could not be found: " + target, ignore); } - if (targetStatus != null && (targetStatus.isDirectory() != sourceCurrStatus.isDirectory())) { + if (targetStatus != null && + (targetStatus.isDirectory() != sourceCurrStatus.isDirectory())) { throw new IOException("Can't replace " + target + ". Target is " + getFileType(targetStatus) + ", Source is " + getFileType(sourceCurrStatus)); } @@ -183,19 +186,28 @@ public class CopyMapper extends Mapper return; } - FileAction action = checkUpdate(sourceFS, sourceCurrStatus, target, targetStatus); + FileAction action = checkUpdate(sourceFS, sourceCurrStatus, target, + targetStatus); + + Path tmpTarget = target; if (action == FileAction.SKIP) { LOG.info("Skipping copy of " + sourceCurrStatus.getPath() + " to " + target); updateSkipCounters(context, sourceCurrStatus); context.write(null, new Text("SKIP: " + sourceCurrStatus.getPath())); + } else { - copyFileWithRetry(description, sourceCurrStatus, target, context, + if (sourceCurrStatus.isSplit()) { + tmpTarget = DistCpUtils.getSplitChunkPath(target, sourceCurrStatus); + } + if (LOG.isDebugEnabled()) { + LOG.debug("copying " + sourceCurrStatus + " " + tmpTarget); + } + copyFileWithRetry(description, sourceCurrStatus, tmpTarget, context, action, fileAttributes); } - - DistCpUtils.preserve(target.getFileSystem(conf), target, sourceCurrStatus, - fileAttributes, preserveRawXattrs); + DistCpUtils.preserve(target.getFileSystem(conf), tmpTarget, + sourceCurrStatus, fileAttributes, preserveRawXattrs); } catch (IOException exception) { handleFailures(exception, sourceFileStatus, target, context); } @@ -261,8 +273,12 @@ public class CopyMapper extends Mapper private void handleFailures(IOException exception, CopyListingFileStatus sourceFileStatus, Path target, Context context) throws IOException, InterruptedException { - LOG.error("Failure in copying " + sourceFileStatus.getPath() + " to " + - target, exception); + LOG.error("Failure in copying " + sourceFileStatus.getPath() + + (sourceFileStatus.isSplit()? "," + + " offset=" + sourceFileStatus.getChunkOffset() + + " chunkLength=" + sourceFileStatus.getChunkLength() + : "") + + " to " + target, exception); if (ignoreFailures && ExceptionUtils.indexOfType(exception, CopyReadException.class) != -1) { diff --git a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/RetriableFileCopyCommand.java b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/RetriableFileCopyCommand.java index 06acd78a8a1..2c17fef1a38 100644 --- a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/RetriableFileCopyCommand.java +++ b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/RetriableFileCopyCommand.java @@ -118,17 +118,21 @@ public class RetriableFileCopyCommand extends RetriableCommand { .contains(FileAttribute.CHECKSUMTYPE) ? sourceFS .getFileChecksum(sourcePath) : null; - final long offset = action == FileAction.APPEND ? targetFS.getFileStatus( - target).getLen() : 0; + long offset = (action == FileAction.APPEND) ? + targetFS.getFileStatus(target).getLen() : source.getChunkOffset(); long bytesRead = copyToFile(targetPath, targetFS, source, offset, context, fileAttributes, sourceChecksum); - compareFileLengths(source, targetPath, configuration, bytesRead - + offset); + if (!source.isSplit()) { + compareFileLengths(source, targetPath, configuration, bytesRead + + offset); + } //At this point, src&dest lengths are same. if length==0, we skip checksum if ((bytesRead != 0) && (!skipCrc)) { - compareCheckSums(sourceFS, source.getPath(), sourceChecksum, - targetFS, targetPath); + if (!source.isSplit()) { + compareCheckSums(sourceFS, source.getPath(), sourceChecksum, + targetFS, targetPath); + } } // it's not append case, thus we first write to a temporary file, rename // it to the target path. @@ -249,16 +253,26 @@ public class RetriableFileCopyCommand extends RetriableCommand { ThrottledInputStream inStream = null; long totalBytesRead = 0; + long chunkLength = source2.getChunkLength(); + boolean finished = false; try { inStream = getInputStream(source, context.getConfiguration()); int bytesRead = readBytes(inStream, buf, sourceOffset); while (bytesRead >= 0) { + if (chunkLength > 0 && + (totalBytesRead + bytesRead) >= chunkLength) { + bytesRead = (int)(chunkLength - totalBytesRead); + finished = true; + } totalBytesRead += bytesRead; if (action == FileAction.APPEND) { sourceOffset += bytesRead; } outStream.write(buf, 0, bytesRead); updateContextStatus(totalBytesRead, context, source2); + if (finished) { + break; + } bytesRead = readBytes(inStream, buf, sourceOffset); } outStream.close(); diff --git a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/UniformSizeInputFormat.java b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/UniformSizeInputFormat.java index 3e86d0931bc..d1c18ea8d16 100644 --- a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/UniformSizeInputFormat.java +++ b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/UniformSizeInputFormat.java @@ -99,7 +99,8 @@ public class UniformSizeInputFormat while (reader.next(srcRelPath, srcFileStatus)) { // If adding the current file would cause the bytes per map to exceed // limit. Add the current file to new split - if (currentSplitSize + srcFileStatus.getLen() > nBytesPerSplit && lastPosition != 0) { + if (currentSplitSize + srcFileStatus.getChunkLength() > nBytesPerSplit + && lastPosition != 0) { FileSplit split = new FileSplit(listingFilePath, lastSplitStart, lastPosition - lastSplitStart, null); if (LOG.isDebugEnabled()) { @@ -109,7 +110,7 @@ public class UniformSizeInputFormat lastSplitStart = lastPosition; currentSplitSize = 0; } - currentSplitSize += srcFileStatus.getLen(); + currentSplitSize += srcFileStatus.getChunkLength(); lastPosition = reader.getPosition(); } if (lastPosition > lastSplitStart) { diff --git a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/util/DistCpUtils.java b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/util/DistCpUtils.java index 76bc4c56268..e315b848de4 100644 --- a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/util/DistCpUtils.java +++ b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/util/DistCpUtils.java @@ -19,9 +19,11 @@ package org.apache.hadoop.tools.util; import com.google.common.collect.Maps; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.BlockLocation; import org.apache.hadoop.fs.FileChecksum; import org.apache.hadoop.fs.FileStatus; import org.apache.hadoop.fs.FileSystem; @@ -30,6 +32,7 @@ import org.apache.hadoop.fs.XAttr; import org.apache.hadoop.fs.permission.AclEntry; import org.apache.hadoop.fs.permission.AclUtil; import org.apache.hadoop.fs.permission.FsPermission; +import org.apache.hadoop.hdfs.DistributedFileSystem; import org.apache.hadoop.io.SequenceFile; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.InputFormat; @@ -44,6 +47,7 @@ import org.apache.hadoop.util.StringUtils; import java.io.IOException; import java.text.DecimalFormat; import java.util.EnumSet; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -116,7 +120,7 @@ public class DistCpUtils { * @return Class implementing the strategy specified in options. */ public static Class getStrategy(Configuration conf, - DistCpOptions options) { + DistCpOptions options) { String confLabel = "distcp." + StringUtils.toLowerCase(options.getCopyStrategy()) + ".strategy" + ".impl"; @@ -297,6 +301,86 @@ public class DistCpUtils { return fileSystem.getXAttrs(path); } + /** + * Converts FileStatus to a list of CopyListingFileStatus. + * The resulted list contains either one CopyListingFileStatus per chunk of + * file-blocks (if file-size exceeds blockSize * blocksPerChunk, and there + * are more blocks in the file than blocksperChunk), or a single + * CopyListingFileStatus for the entire file (if file-size is too small to + * split). + * If preserving ACLs, populates the CopyListingFileStatus with the ACLs. + * If preserving XAttrs, populates the CopyListingFileStatus with the XAttrs. + * + * @param fileSystem FileSystem containing the file + * @param fileStatus FileStatus of file + * @param preserveAcls boolean true if preserving ACLs + * @param preserveXAttrs boolean true if preserving XAttrs + * @param preserveRawXAttrs boolean true if preserving raw.* XAttrs + * @param blocksPerChunk size of chunks when copying chunks in parallel + * @return list of CopyListingFileStatus + * @throws IOException if there is an I/O error + */ + public static LinkedList toCopyListingFileStatus( + FileSystem fileSystem, FileStatus fileStatus, boolean preserveAcls, + boolean preserveXAttrs, boolean preserveRawXAttrs, int blocksPerChunk) + throws IOException { + LinkedList copyListingFileStatus = + new LinkedList(); + + final CopyListingFileStatus clfs = toCopyListingFileStatusHelper( + fileSystem, fileStatus, preserveAcls, + preserveXAttrs, preserveRawXAttrs, + 0, fileStatus.getLen()); + final long blockSize = fileStatus.getBlockSize(); + if (LOG.isDebugEnabled()) { + LOG.debug("toCopyListing: " + fileStatus + " chunkSize: " + + blocksPerChunk + " isDFS: " + + (fileSystem instanceof DistributedFileSystem)); + } + if ((blocksPerChunk > 0) && + !fileStatus.isDirectory() && + (fileStatus.getLen() > blockSize * blocksPerChunk)) { + // split only when the file size is larger than the intended chunk size + final BlockLocation[] blockLocations; + blockLocations = fileSystem.getFileBlockLocations(fileStatus, 0, + fileStatus.getLen()); + + int numBlocks = blockLocations.length; + long curPos = 0; + if (numBlocks <= blocksPerChunk) { + if (LOG.isDebugEnabled()) { + LOG.debug(" add file " + clfs); + } + copyListingFileStatus.add(clfs); + } else { + int i = 0; + while (i < numBlocks) { + long curLength = 0; + for (int j = 0; j < blocksPerChunk && i < numBlocks; ++j, ++i) { + curLength += blockLocations[i].getLength(); + } + if (curLength > 0) { + CopyListingFileStatus clfs1 = new CopyListingFileStatus(clfs); + clfs1.setChunkOffset(curPos); + clfs1.setChunkLength(curLength); + if (LOG.isDebugEnabled()) { + LOG.debug(" add file chunk " + clfs1); + } + copyListingFileStatus.add(clfs1); + curPos += curLength; + } + } + } + } else { + if (LOG.isDebugEnabled()) { + LOG.debug(" add file/dir " + clfs); + } + copyListingFileStatus.add(clfs); + } + + return copyListingFileStatus; + } + /** * Converts a FileStatus to a CopyListingFileStatus. If preserving ACLs, * populates the CopyListingFileStatus with the ACLs. If preserving XAttrs, @@ -307,13 +391,17 @@ public class DistCpUtils { * @param preserveAcls boolean true if preserving ACLs * @param preserveXAttrs boolean true if preserving XAttrs * @param preserveRawXAttrs boolean true if preserving raw.* XAttrs + * @param chunkOffset chunk offset in bytes + * @param chunkLength chunk length in bytes + * @return CopyListingFileStatus * @throws IOException if there is an I/O error */ - public static CopyListingFileStatus toCopyListingFileStatus( + public static CopyListingFileStatus toCopyListingFileStatusHelper( FileSystem fileSystem, FileStatus fileStatus, boolean preserveAcls, - boolean preserveXAttrs, boolean preserveRawXAttrs) throws IOException { + boolean preserveXAttrs, boolean preserveRawXAttrs, + long chunkOffset, long chunkLength) throws IOException { CopyListingFileStatus copyListingFileStatus = - new CopyListingFileStatus(fileStatus); + new CopyListingFileStatus(fileStatus, chunkOffset, chunkLength); if (preserveAcls) { FsPermission perm = fileStatus.getPermission(); if (perm.getAclBit()) { @@ -470,4 +558,19 @@ public class DistCpUtils { return (sourceChecksum == null || targetChecksum == null || sourceChecksum.equals(targetChecksum)); } + + /* + * Return the Path for a given chunk. + * Used when splitting large file into chunks to copy in parallel. + * @param targetFile path to target file + * @param srcFileStatus source file status in copy listing + * @return path to the chunk specified by the parameters to store + * in target cluster temporarily + */ + public static Path getSplitChunkPath(Path targetFile, + CopyListingFileStatus srcFileStatus) { + return new Path(targetFile.toString() + + ".____distcpSplit____" + srcFileStatus.getChunkOffset() + + "." + srcFileStatus.getChunkLength()); + } } diff --git a/hadoop-tools/hadoop-distcp/src/site/markdown/DistCp.md.vm b/hadoop-tools/hadoop-distcp/src/site/markdown/DistCp.md.vm index 41a6e94aeee..a77deb2ffee 100644 --- a/hadoop-tools/hadoop-distcp/src/site/markdown/DistCp.md.vm +++ b/hadoop-tools/hadoop-distcp/src/site/markdown/DistCp.md.vm @@ -237,6 +237,7 @@ Flag | Description | Notes `-rdiff ` | Use snapshot diff report between given two snapshots to identify what has been changed on the target since the snapshot `` was created on the target, and apply the diff reversely to the target, and copy modified files from the source's ``, to make the target the same as ``. | This option is valid only with `-update` option and the following conditions should be satisfied.
    1. Both the source and the target FileSystem must be DistributedFileSystem. The source and the target can be two different clusters/paths, or they can be exactly the same cluster/path. In the latter case, modified files are copied from target's `` to target's current state).
    2. Two snapshots `` and `` have been created on the target FS, and `` is older than ``. No change has been made on target since `` was created on the target.
    3. The source has the same snapshot ``, which has the same content as the `` on the target. All the files/directories in the target's `` are the same with source's ``.
    | `-numListstatusThreads` | Number of threads to use for building file listing | At most 40 threads. `-skipcrccheck` | Whether to skip CRC checks between source and target paths. | +`-blocksperchunk ` | Number of blocks per chunk. When specified, split files into chunks to copy in parallel | If set to a positive value, files with more blocks than this value will be split into chunks of `` blocks to be transferred in parallel, and reassembled on the destination. By default, `` is 0 and the files will be transmitted in their entirety without splitting. This switch is only applicable when the source file system implements getBlockLocations method and the target file system implements concat method. | Architecture of DistCp ---------------------- diff --git a/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestDistCpSystem.java b/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestDistCpSystem.java index e3018a07730..b2266b3344d 100644 --- a/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestDistCpSystem.java +++ b/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestDistCpSystem.java @@ -23,17 +23,27 @@ import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.OutputStream; +import java.io.PrintStream; import java.net.URI; import java.util.ArrayList; import java.util.List; +import java.util.Random; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.FSDataInputStream; import org.apache.hadoop.fs.FileStatus; import org.apache.hadoop.fs.FileSystem; +import org.apache.hadoop.fs.FsShell; import org.apache.hadoop.fs.Path; +import org.apache.hadoop.hdfs.DFSConfigKeys; +import org.apache.hadoop.hdfs.DFSTestUtil; +import org.apache.hadoop.hdfs.DistributedFileSystem; import org.apache.hadoop.hdfs.MiniDFSCluster; +import org.apache.hadoop.hdfs.client.HdfsClientConfigKeys; import org.apache.hadoop.util.ToolRunner; import org.junit.AfterClass; import org.junit.Assert; @@ -47,11 +57,15 @@ import org.junit.rules.Timeout; */ public class TestDistCpSystem { + private static final Log LOG = + LogFactory.getLog(TestDistCpSystem.class); + @Rule public Timeout globalTimeout = new Timeout(30000); private static final String SRCDAT = "srcdat"; private static final String DSTDAT = "dstdat"; + private static final long BLOCK_SIZE = 1024; private static MiniDFSCluster cluster; private static Configuration conf; @@ -63,27 +77,76 @@ public class TestDistCpSystem { this.path = path; this.isDir = isDir; } - String getPath() { return path; } - boolean isDirectory() { return isDir; } + + String getPath() { + return path; + } + + boolean isDirectory() { + return isDir; + } + } + + @BeforeClass + public static void beforeClass() throws IOException { + conf = new Configuration(); + conf.setLong(DFSConfigKeys.DFS_NAMENODE_MIN_BLOCK_SIZE_KEY, BLOCK_SIZE); + conf.setLong(DFSConfigKeys.DFS_BLOCK_SIZE_KEY, BLOCK_SIZE); + cluster = new MiniDFSCluster.Builder(conf).numDataNodes(2).build(); + cluster.waitActive(); + } + + @AfterClass + public static void afterClass() throws IOException { + if (cluster != null) { + cluster.shutdown(); + } + } + + static String execCmd(FsShell shell, String... args) throws Exception { + ByteArrayOutputStream baout = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(baout, true); + PrintStream old = System.out; + System.setOut(out); + shell.run(args); + out.close(); + System.setOut(old); + return baout.toString(); } - private void createFiles(FileSystem fs, String topdir, - FileEntry[] entries) throws IOException { + private void createFiles(DistributedFileSystem fs, String topdir, + FileEntry[] entries, long chunkSize) throws IOException { + long seed = System.currentTimeMillis(); + Random rand = new Random(seed); + short replicationFactor = 2; for (FileEntry entry : entries) { - Path newpath = new Path(topdir + "/" + entry.getPath()); + Path newPath = new Path(topdir + "/" + entry.getPath()); if (entry.isDirectory()) { - fs.mkdirs(newpath); + fs.mkdirs(newPath); } else { - OutputStream out = fs.create(newpath); - try { - out.write((topdir + "/" + entry).getBytes()); - out.write("\n".getBytes()); - } finally { - out.close(); + long fileSize = BLOCK_SIZE *100; + int bufSize = 128; + if (chunkSize == -1) { + DFSTestUtil.createFile(fs, newPath, bufSize, + fileSize, BLOCK_SIZE, replicationFactor, seed); + } else { + // Create a variable length block file, by creating + // one block of half block size at the chunk boundary + long seg1 = chunkSize * BLOCK_SIZE - BLOCK_SIZE / 2; + long seg2 = fileSize - seg1; + DFSTestUtil.createFile(fs, newPath, bufSize, + seg1, BLOCK_SIZE, replicationFactor, seed); + DFSTestUtil.appendFileNewBlock(fs, newPath, (int)seg2); } } + seed = System.currentTimeMillis() + rand.nextLong(); } } + + private void createFiles(DistributedFileSystem fs, String topdir, + FileEntry[] entries) throws IOException { + createFiles(fs, topdir, entries, -1); + } private static FileStatus[] getFileStatus(FileSystem fs, String topdir, FileEntry[] files) throws IOException { @@ -104,18 +167,19 @@ public class TestDistCpSystem { } private void testPreserveUserHelper(String testRoot, - FileEntry[] srcEntries, - FileEntry[] dstEntries, - boolean createSrcDir, - boolean createTgtDir, - boolean update) throws Exception { + FileEntry[] srcEntries, + FileEntry[] dstEntries, + boolean createSrcDir, + boolean createTgtDir, + boolean update) throws Exception { final String testSrcRel = SRCDAT; final String testSrc = testRoot + "/" + testSrcRel; final String testDstRel = DSTDAT; final String testDst = testRoot + "/" + testDstRel; String nnUri = FileSystem.getDefaultUri(conf).toString(); - FileSystem fs = FileSystem.get(URI.create(nnUri), conf); + DistributedFileSystem fs = (DistributedFileSystem) + FileSystem.get(URI.create(nnUri), conf); fs.mkdirs(new Path(testRoot)); if (createSrcDir) { fs.mkdirs(new Path(testSrc)); @@ -129,8 +193,8 @@ public class TestDistCpSystem { for(int i = 0; i < srcEntries.length; i++) { fs.setOwner(srcstats[i].getPath(), "u" + i, null); } - String[] args = update? new String[]{"-pu", "-update", nnUri+testSrc, - nnUri+testDst} : new String[]{"-pu", nnUri+testSrc, nnUri+testDst}; + String[] args = update? new String[]{"-pub", "-update", nnUri+testSrc, + nnUri+testDst} : new String[]{"-pub", nnUri+testSrc, nnUri+testDst}; ToolRunner.run(conf, new DistCp(), args); @@ -145,20 +209,263 @@ public class TestDistCpSystem { deldir(fs, testRoot); } - @BeforeClass - public static void beforeClass() throws IOException { - conf = new Configuration(); - cluster = new MiniDFSCluster.Builder(conf).numDataNodes(2).build(); - cluster.waitActive(); + private void compareFiles(FileSystem fs, FileStatus srcStat, + FileStatus dstStat) throws Exception { + LOG.info("Comparing " + srcStat + " and " + dstStat); + assertEquals(srcStat.isDirectory(), dstStat.isDirectory()); + assertEquals(srcStat.getReplication(), dstStat.getReplication()); + assertEquals("File POSIX permission should match", + srcStat.getPermission(), dstStat.getPermission()); + assertEquals("File user ownership should match", + srcStat.getOwner(), dstStat.getOwner()); + assertEquals("File group ownership should match", + srcStat.getGroup(), dstStat.getGroup()); + // TODO; check ACL attributes + + if (srcStat.isDirectory()) { + return; + } + + assertEquals("File length should match (" + srcStat.getPath() + ")", + srcStat.getLen(), dstStat.getLen()); + + FSDataInputStream srcIn = fs.open(srcStat.getPath()); + FSDataInputStream dstIn = fs.open(dstStat.getPath()); + try { + byte[] readSrc = new byte[(int) + HdfsClientConfigKeys.DFS_BLOCK_SIZE_DEFAULT]; + byte[] readDst = new byte[(int) + HdfsClientConfigKeys.DFS_BLOCK_SIZE_DEFAULT]; + + int srcBytesRead = 0, tgtBytesRead = 0; + int srcIdx = 0, tgtIdx = 0; + long totalComparedBytes = 0; + while (true) { + if (srcBytesRead == 0) { + srcBytesRead = srcIn.read(readSrc); + srcIdx = 0; + } + if (tgtBytesRead == 0) { + tgtBytesRead = dstIn.read(readDst); + tgtIdx = 0; + } + if (srcBytesRead == 0 || tgtBytesRead == 0) { + LOG.info("______ compared src and dst files for " + + totalComparedBytes + " bytes, content match."); + if (srcBytesRead != tgtBytesRead) { + Assert.fail("Read mismatching size, compared " + + totalComparedBytes + " bytes between src and dst file " + + srcStat + " and " + dstStat); + } + if (totalComparedBytes != srcStat.getLen()) { + Assert.fail("Only read/compared " + totalComparedBytes + + " bytes between src and dst file " + srcStat + + " and " + dstStat); + } else { + // success + break; + } + } + for (; srcIdx < srcBytesRead && tgtIdx < tgtBytesRead; + ++srcIdx, ++tgtIdx) { + if (readSrc[srcIdx] != readDst[tgtIdx]) { + Assert.fail("src and dst file does not match at " + + totalComparedBytes + " between " + + srcStat + " and " + dstStat); + } + ++totalComparedBytes; + } + LOG.info("______ compared src and dst files for " + + totalComparedBytes + " bytes, content match. FileLength: " + + srcStat.getLen()); + if (totalComparedBytes == srcStat.getLen()) { + LOG.info("______ Final:" + srcIdx + " " + + srcBytesRead + " " + tgtIdx + " " + tgtBytesRead); + break; + } + if (srcIdx == srcBytesRead) { + srcBytesRead = 0; + } + if (tgtIdx == tgtBytesRead) { + tgtBytesRead = 0; + } + } + } finally { + if (srcIn != null) { + srcIn.close(); + } + if (dstIn != null) { + dstIn.close(); + } + } } - @AfterClass - public static void afterClass() throws IOException { - if (cluster != null) { - cluster.shutdown(); + // WC: needed because the current distcp does not create target dirs + private void createDestDir(FileSystem fs, String testDst, + FileStatus[] srcStats, FileEntry[] srcFiles) throws IOException { + fs.mkdirs(new Path(testDst)); + + for (int i=0; i=0; --i) { + if (!srcFiles[i].isDirectory()) { + LOG.info("Modifying " + srcStats[i].getPath()); + DFSTestUtil.appendFileNewBlock(fs, srcStats[i].getPath(), + (int)BLOCK_SIZE * 3); + break; + } + } + // get file status after modifying file + srcStats = getFileStatus(fs, testRoot, srcFiles); + + args = new String[] {"-pugp", "-update", "-blocksperchunk", + String.valueOf(chunkSize), + nnUri + testSrc, nnUri + testDst + "/" + testSrcRel}; + + copyAndVerify(fs, srcFiles, srcStats, testDst, args); + + deldir(fs, testRoot); + } + + @Test + public void testRecursiveChunkCopy() throws Exception { + FileEntry[] srcFiles = { + new FileEntry(SRCDAT, true), + new FileEntry(SRCDAT + "/file0", false), + new FileEntry(SRCDAT + "/dir1", true), + new FileEntry(SRCDAT + "/dir2", true), + new FileEntry(SRCDAT + "/dir1/file1", false) + }; + chunkCopy(srcFiles); + } + + @Test + public void testChunkCopyOneFile() throws Exception { + FileEntry[] srcFiles = { + new FileEntry(SRCDAT, true), + new FileEntry(SRCDAT + "/file0", false) + }; + chunkCopy(srcFiles); + } + + @Test + public void testDistcpLargeFile() throws Exception { + FileEntry[] srcfiles = { + new FileEntry(SRCDAT, true), + new FileEntry(SRCDAT + "/file", false) + }; + + final String testRoot = "/testdir"; + final String testSrcRel = SRCDAT; + final String testSrc = testRoot + "/" + testSrcRel; + final String testDstRel = DSTDAT; + final String testDst = testRoot + "/" + testDstRel; + + String nnUri = FileSystem.getDefaultUri(conf).toString(); + DistributedFileSystem fs = + (DistributedFileSystem) FileSystem.get(URI.create(nnUri), conf); + fs.mkdirs(new Path(testRoot)); + fs.mkdirs(new Path(testSrc)); + fs.mkdirs(new Path(testDst)); + long chunkSize = 6; + createFiles(fs, testRoot, srcfiles, chunkSize); + + String srcFileName = testRoot + Path.SEPARATOR + srcfiles[1].getPath(); + Path srcfile = new Path(srcFileName); + + if(!cluster.getFileSystem().exists(srcfile)){ + throw new Exception("src not exist"); + } + + final long srcLen = fs.getFileStatus(srcfile).getLen(); + + FileStatus[] srcstats = getFileStatus(fs, testRoot, srcfiles); + for (int i = 0; i < srcfiles.length; i++) { + fs.setOwner(srcstats[i].getPath(), "u" + i, null); + } + String[] args = new String[] { + "-blocksperchunk", + String.valueOf(chunkSize), + nnUri + testSrc, + nnUri + testDst + }; + + LOG.info("_____ running distcp: " + args[0] + " " + args[1]); + ToolRunner.run(conf, new DistCp(), args); + + String realTgtPath = testDst; + FileStatus[] dststat = getFileStatus(fs, realTgtPath, srcfiles); + assertEquals("File length should match", srcLen, + dststat[dststat.length - 1].getLen()); + + this.compareFiles(fs, srcstats[srcstats.length-1], + dststat[dststat.length-1]); + deldir(fs, testRoot); + } + @Test public void testPreserveUseNonEmptyDir() throws Exception { String testRoot = "/testdir." + getMethodName(); @@ -180,7 +487,6 @@ public class TestDistCpSystem { testPreserveUserHelper(testRoot, srcfiles, dstfiles, false, false, false); } - @Test public void testPreserveUserEmptyDir() throws Exception { String testRoot = "/testdir." + getMethodName(); diff --git a/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestOptionsParser.java b/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestOptionsParser.java index efe46272e38..f94ba97ec34 100644 --- a/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestOptionsParser.java +++ b/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestOptionsParser.java @@ -394,7 +394,7 @@ public class TestOptionsParser { + "copyStrategy='uniformsize', preserveStatus=[], " + "preserveRawXattrs=false, atomicWorkPath=null, logPath=null, " + "sourceFileListing=abc, sourcePaths=null, targetPath=xyz, " - + "targetPathExists=true, filtersFile='null'}"; + + "targetPathExists=true, filtersFile='null', blocksPerChunk=0}"; String optionString = option.toString(); Assert.assertEquals(val, optionString); Assert.assertNotSame(DistCpOptionSwitch.ATOMIC_COMMIT.toString(), diff --git a/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/mapred/TestCopyCommitter.java b/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/mapred/TestCopyCommitter.java index 2e9a350b1ca..2452d6fee3d 100644 --- a/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/mapred/TestCopyCommitter.java +++ b/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/mapred/TestCopyCommitter.java @@ -81,6 +81,10 @@ public class TestCopyCommitter { @Before public void createMetaFolder() { config.set(DistCpConstants.CONF_LABEL_META_FOLDER, "/meta"); + // Unset listing file path since the config is shared by + // multiple tests, and some test doesn't set it, such as + // testNoCommitAction, but the distcp code will check it. + config.set(DistCpConstants.CONF_LABEL_LISTING_FILE_PATH, ""); Path meta = new Path("/meta"); try { cluster.getFileSystem().mkdirs(meta); @@ -326,7 +330,6 @@ public class TestCopyCommitter { committer.commitJob(jobContext); Assert.assertFalse(fs.exists(new Path(workPath))); Assert.assertTrue(fs.exists(new Path(finalPath))); - } catch (IOException e) { LOG.error("Exception encountered while testing for preserve status", e); Assert.fail("Atomic commit failure"); From 144f1cf76527e6c75aec77ef683a898580f3cc8d Mon Sep 17 00:00:00 2001 From: Yongjun Zhang Date: Thu, 30 Mar 2017 17:38:18 -0700 Subject: [PATCH 172/188] Revert "HADOOP-11794. Enable distcp to copy blocks in parallel. Contributed by Yongjun Zhang, Wei-Chiu Chuang, Xiao Chen." This reverts commit 064c8b25eca9bc825dc07a54d9147d65c9290a03. --- .../org/apache/hadoop/hdfs/DFSTestUtil.java | 22 +- .../org/apache/hadoop/tools/CopyListing.java | 37 +- .../hadoop/tools/CopyListingFileStatus.java | 87 +---- .../java/org/apache/hadoop/tools/DistCp.java | 52 --- .../hadoop/tools/DistCpOptionSwitch.java | 10 - .../apache/hadoop/tools/DistCpOptions.java | 22 +- .../apache/hadoop/tools/OptionsParser.java | 36 +- .../hadoop/tools/SimpleCopyListing.java | 83 ++-- .../hadoop/tools/mapred/CopyCommitter.java | 174 +-------- .../hadoop/tools/mapred/CopyMapper.java | 40 +- .../mapred/RetriableFileCopyCommand.java | 26 +- .../tools/mapred/UniformSizeInputFormat.java | 5 +- .../apache/hadoop/tools/util/DistCpUtils.java | 111 +----- .../src/site/markdown/DistCp.md.vm | 1 - .../apache/hadoop/tools/TestDistCpSystem.java | 368 ++---------------- .../hadoop/tools/TestOptionsParser.java | 2 +- .../tools/mapred/TestCopyCommitter.java | 5 +- 17 files changed, 110 insertions(+), 971 deletions(-) diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/DFSTestUtil.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/DFSTestUtil.java index 9b782f32f2a..13291952a9e 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/DFSTestUtil.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/DFSTestUtil.java @@ -862,27 +862,7 @@ public class DFSTestUtil { out.write(toAppend); } } - - /** - * Append specified length of bytes to a given file, starting with new block. - * @param fs The file system - * @param p Path of the file to append - * @param length Length of bytes to append to the file - * @throws IOException - */ - public static void appendFileNewBlock(DistributedFileSystem fs, - Path p, int length) throws IOException { - assert fs.exists(p); - assert length >= 0; - byte[] toAppend = new byte[length]; - Random random = new Random(); - random.nextBytes(toAppend); - try (FSDataOutputStream out = fs.append(p, - EnumSet.of(CreateFlag.APPEND, CreateFlag.NEW_BLOCK), 4096, null)) { - out.write(toAppend); - } - } - + /** * @return url content as string (UTF-8 encoding assumed) */ diff --git a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/CopyListing.java b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/CopyListing.java index 9ebf9d29b3b..481aa61b0f1 100644 --- a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/CopyListing.java +++ b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/CopyListing.java @@ -145,22 +145,12 @@ public abstract class CopyListing extends Configured { Configuration config = getConf(); FileSystem fs = pathToListFile.getFileSystem(config); - final boolean splitLargeFile = options.splitLargeFile(); - - // When splitLargeFile is enabled, we don't randomize the copylist - // earlier, so we don't do the sorting here. For a file that has - // multiple entries due to split, we check here that their - // is continuous. - // - Path checkPath = splitLargeFile? - pathToListFile : DistCpUtils.sortListing(fs, config, pathToListFile); + Path sortedList = DistCpUtils.sortListing(fs, config, pathToListFile); SequenceFile.Reader reader = new SequenceFile.Reader( - config, SequenceFile.Reader.file(checkPath)); + config, SequenceFile.Reader.file(sortedList)); try { Text lastKey = new Text("*"); //source relative path can never hold * - long lastChunkOffset = -1; - long lastChunkLength = -1; CopyListingFileStatus lastFileStatus = new CopyListingFileStatus(); Text currentKey = new Text(); @@ -171,21 +161,8 @@ public abstract class CopyListing extends Configured { if (currentKey.equals(lastKey)) { CopyListingFileStatus currentFileStatus = new CopyListingFileStatus(); reader.getCurrentValue(currentFileStatus); - if (!splitLargeFile) { - throw new DuplicateFileException("File " + lastFileStatus.getPath() - + " and " + currentFileStatus.getPath() - + " would cause duplicates. Aborting"); - } else { - if (lastChunkOffset + lastChunkLength != - currentFileStatus.getChunkOffset()) { - throw new InvalidInputException("File " + lastFileStatus.getPath() - + " " + lastChunkOffset + "," + lastChunkLength - + " and " + currentFileStatus.getPath() - + " " + currentFileStatus.getChunkOffset() + "," - + currentFileStatus.getChunkLength() - + " are not continuous. Aborting"); - } - } + throw new DuplicateFileException("File " + lastFileStatus.getPath() + " and " + + currentFileStatus.getPath() + " would cause duplicates. Aborting"); } reader.getCurrentValue(lastFileStatus); if (options.shouldPreserve(DistCpOptions.FileAttribute.ACL)) { @@ -204,12 +181,8 @@ public abstract class CopyListing extends Configured { xAttrSupportCheckFsSet.add(lastFsUri); } } - lastKey.set(currentKey); - if (splitLargeFile) { - lastChunkOffset = lastFileStatus.getChunkOffset(); - lastChunkLength = lastFileStatus.getChunkLength(); - } + if (options.shouldUseDiff() && LOG.isDebugEnabled()) { LOG.debug("Copy list entry " + idx + ": " + lastFileStatus.getPath().toUri().getPath()); diff --git a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/CopyListingFileStatus.java b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/CopyListingFileStatus.java index 29c59ac1033..00d4b325053 100644 --- a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/CopyListingFileStatus.java +++ b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/CopyListingFileStatus.java @@ -74,14 +74,6 @@ public final class CopyListingFileStatus implements Writable { private List aclEntries; private Map xAttrs; - // represents the offset and length of a file - // chunk in number of bytes. - // used when splitting a large file to chunks to copy in parallel. - // If a file is not large enough to split, chunkOffset would be 0 and - // chunkLength would be the length of the file. - private long chunkOffset = 0; - private long chunkLength = Long.MAX_VALUE; - /** * Default constructor. */ @@ -104,32 +96,11 @@ public final class CopyListingFileStatus implements Writable { fileStatus.getPath()); } - public CopyListingFileStatus(FileStatus fileStatus, - long chunkOffset, long chunkLength) { - this(fileStatus.getLen(), fileStatus.isDirectory(), - fileStatus.getReplication(), fileStatus.getBlockSize(), - fileStatus.getModificationTime(), fileStatus.getAccessTime(), - fileStatus.getPermission(), fileStatus.getOwner(), - fileStatus.getGroup(), - fileStatus.getPath()); - this.chunkOffset = chunkOffset; - this.chunkLength = chunkLength; - } - @SuppressWarnings("checkstyle:parameternumber") public CopyListingFileStatus(long length, boolean isdir, int blockReplication, long blocksize, long modificationTime, long accessTime, FsPermission permission, String owner, String group, Path path) { - this(length, isdir, blockReplication, blocksize, modificationTime, - accessTime, permission, owner, group, path, 0, Long.MAX_VALUE); - } - - @SuppressWarnings("checkstyle:parameternumber") - public CopyListingFileStatus(long length, boolean isdir, - int blockReplication, long blocksize, long modificationTime, - long accessTime, FsPermission permission, String owner, String group, - Path path, long chunkOffset, long chunkLength) { this.length = length; this.isdir = isdir; this.blockReplication = (short)blockReplication; @@ -146,23 +117,6 @@ public final class CopyListingFileStatus implements Writable { this.owner = (owner == null) ? "" : owner; this.group = (group == null) ? "" : group; this.path = path; - this.chunkOffset = chunkOffset; - this.chunkLength = chunkLength; - } - - public CopyListingFileStatus(CopyListingFileStatus other) { - this.length = other.length; - this.isdir = other.isdir; - this.blockReplication = other.blockReplication; - this.blocksize = other.blocksize; - this.modificationTime = other.modificationTime; - this.accessTime = other.accessTime; - this.permission = other.permission; - this.owner = other.owner; - this.group = other.group; - this.path = new Path(other.path.toUri()); - this.chunkOffset = other.chunkOffset; - this.chunkLength = other.chunkLength; } public Path getPath() { @@ -246,31 +200,6 @@ public final class CopyListingFileStatus implements Writable { this.xAttrs = xAttrs; } - public long getChunkOffset() { - return chunkOffset; - } - - public void setChunkOffset(long offset) { - this.chunkOffset = offset; - } - - public long getChunkLength() { - return chunkLength; - } - - public void setChunkLength(long chunkLength) { - this.chunkLength = chunkLength; - } - - public boolean isSplit() { - return getChunkLength() != Long.MAX_VALUE && - getChunkLength() != getLen(); - } - - public long getSizeToCopy() { - return isSplit()? getChunkLength() : getLen(); - } - @Override public void write(DataOutput out) throws IOException { Text.writeString(out, getPath().toString(), Text.DEFAULT_MAX_LEN); @@ -315,9 +244,6 @@ public final class CopyListingFileStatus implements Writable { } else { out.writeInt(NO_XATTRS); } - - out.writeLong(chunkOffset); - out.writeLong(chunkLength); } @Override @@ -366,9 +292,6 @@ public final class CopyListingFileStatus implements Writable { } else { xAttrs = null; } - - chunkOffset = in.readLong(); - chunkLength = in.readLong(); } @Override @@ -394,14 +317,8 @@ public final class CopyListingFileStatus implements Writable { public String toString() { StringBuilder sb = new StringBuilder(super.toString()); sb.append('{'); - sb.append(this.getPath().toString()); - sb.append(" length = ").append(this.getLen()); - sb.append(" aclEntries = ").append(aclEntries); - sb.append(", xAttrs = ").append(xAttrs); - if (isSplit()) { - sb.append(", chunkOffset = ").append(this.getChunkOffset()); - sb.append(", chunkLength = ").append(this.getChunkLength()); - } + sb.append("aclEntries = " + aclEntries); + sb.append(", xAttrs = " + xAttrs); sb.append('}'); return sb.toString(); } diff --git a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCp.java b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCp.java index 8c2fa24a15c..ab58e9c66d8 100644 --- a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCp.java +++ b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCp.java @@ -35,7 +35,6 @@ import org.apache.hadoop.mapreduce.Cluster; import org.apache.hadoop.mapreduce.Job; import org.apache.hadoop.mapreduce.JobContext; import org.apache.hadoop.mapreduce.JobSubmissionFiles; -import org.apache.hadoop.tools.DistCpOptions.FileAttribute; import org.apache.hadoop.tools.CopyListing.*; import org.apache.hadoop.tools.mapred.CopyMapper; import org.apache.hadoop.tools.mapred.CopyOutputFormat; @@ -135,7 +134,6 @@ public class DistCp extends Configured implements Tool { try { inputOptions = (OptionsParser.parse(argv)); - setOptionsForSplitLargeFile(); setTargetPathExists(); LOG.info("Input Options: " + inputOptions); } catch (Throwable e) { @@ -237,56 +235,6 @@ public class DistCp extends Configured implements Tool { getConf().setBoolean(DistCpConstants.CONF_LABEL_TARGET_PATH_EXISTS, targetExists); } - - /** - * Check if concat is supported by fs. - * Throws UnsupportedOperationException if not. - */ - private void checkConcatSupport(FileSystem fs) { - try { - Path[] src = null; - Path tgt = null; - fs.concat(tgt, src); - } catch (UnsupportedOperationException use) { - throw new UnsupportedOperationException( - DistCpOptionSwitch.BLOCKS_PER_CHUNK.getSwitch() + - " is not supported since the target file system doesn't" + - " support concat.", use); - } catch (Exception e) { - // Ignore other exception - } - } - - /** - * Set up needed options for splitting large files. - */ - private void setOptionsForSplitLargeFile() throws IOException { - if (!inputOptions.splitLargeFile()) { - return; - } - Path target = inputOptions.getTargetPath(); - FileSystem targetFS = target.getFileSystem(getConf()); - checkConcatSupport(targetFS); - - LOG.info("Enabling preserving blocksize since " - + DistCpOptionSwitch.BLOCKS_PER_CHUNK.getSwitch() + " is passed."); - inputOptions.preserve(FileAttribute.BLOCKSIZE); - - LOG.info("Set " + - DistCpOptionSwitch.APPEND.getSwitch() - + " to false since " + DistCpOptionSwitch.BLOCKS_PER_CHUNK.getSwitch() - + " is passed."); - inputOptions.setAppend(false); - - LOG.info("Set " + - DistCpConstants.CONF_LABEL_SIMPLE_LISTING_RANDOMIZE_FILES - + " to false since " + DistCpOptionSwitch.BLOCKS_PER_CHUNK.getSwitch() - + " is passed."); - getConf().setBoolean( - DistCpConstants.CONF_LABEL_SIMPLE_LISTING_RANDOMIZE_FILES, false); - } - - /** * Create Job object for submitting it, with all the configuration * diff --git a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCpOptionSwitch.java b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCpOptionSwitch.java index ced9b540c89..fb47d769233 100644 --- a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCpOptionSwitch.java +++ b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCpOptionSwitch.java @@ -169,16 +169,6 @@ public enum DistCpOptionSwitch { new Option("sizelimit", true, "(Deprecated!) Limit number of files " + "copied to <= n bytes")), - BLOCKS_PER_CHUNK("", - new Option("blocksperchunk", true, "If set to a positive value, files" - + "with more blocks than this value will be split into chunks of " - + " blocks to be transferred in parallel, and " - + "reassembled on the destination. By default, is " - + "0 and the files will be transmitted in their entirety without " - + "splitting. This switch is only applicable when the source file " - + "system implements getBlockLocations method and the target file " - + "system implements concat method")), - /** * Specify bandwidth per map in MB, accepts bandwidth as a fraction */ diff --git a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCpOptions.java b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCpOptions.java index 9822d83dbd0..8c37ff30ae0 100644 --- a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCpOptions.java +++ b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCpOptions.java @@ -97,11 +97,7 @@ public class DistCpOptions { // targetPathExist is a derived field, it's initialized in the // beginning of distcp. private boolean targetPathExists = true; - - // Size of chunk in number of blocks when splitting large file into chunks - // to copy in parallel. Default is 0 and file are not splitted. - private int blocksPerChunk = 0; - + public static enum FileAttribute{ REPLICATION, BLOCKSIZE, USER, GROUP, PERMISSION, CHECKSUMTYPE, ACL, XATTR, TIMES; @@ -170,7 +166,6 @@ public class DistCpOptions { this.targetPath = that.getTargetPath(); this.targetPathExists = that.getTargetPathExists(); this.filtersFile = that.getFiltersFile(); - this.blocksPerChunk = that.blocksPerChunk; } } @@ -583,18 +578,6 @@ public class DistCpOptions { this.filtersFile = filtersFilename; } - public final void setBlocksPerChunk(int csize) { - this.blocksPerChunk = csize; - } - - public final int getBlocksPerChunk() { - return blocksPerChunk; - } - - public final boolean splitLargeFile() { - return blocksPerChunk > 0; - } - void validate() { if ((useDiff || useRdiff) && deleteMissing) { // -delete and -diff/-rdiff are mutually exclusive. For backward @@ -686,8 +669,6 @@ public class DistCpOptions { DistCpOptionSwitch.addToConf(conf, DistCpOptionSwitch.FILTERS, filtersFile); } - DistCpOptionSwitch.addToConf(conf, DistCpOptionSwitch.BLOCKS_PER_CHUNK, - String.valueOf(blocksPerChunk)); } /** @@ -723,7 +704,6 @@ public class DistCpOptions { ", targetPath=" + targetPath + ", targetPathExists=" + targetPathExists + ", filtersFile='" + filtersFile + '\'' + - ", blocksPerChunk=" + blocksPerChunk + '}'; } diff --git a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/OptionsParser.java b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/OptionsParser.java index 8881264d281..d0f82ca76b5 100644 --- a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/OptionsParser.java +++ b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/OptionsParser.java @@ -172,44 +172,11 @@ public class OptionsParser { DistCpOptionSwitch.FILTERS.getSwitch())); } - parseBlocksPerChunk(command, option); - option.validate(); return option; } - - /** - * A helper method to parse chunk size in number of blocks. - * Used when breaking large file into chunks to copy in parallel. - * - * @param command command line arguments - */ - private static void parseBlocksPerChunk(CommandLine command, - DistCpOptions option) { - boolean hasOption = - command.hasOption(DistCpOptionSwitch.BLOCKS_PER_CHUNK.getSwitch()); - LOG.info("parseChunkSize: " + - DistCpOptionSwitch.BLOCKS_PER_CHUNK.getSwitch() + " " + hasOption); - if (hasOption) { - String chunkSizeString = getVal(command, - DistCpOptionSwitch.BLOCKS_PER_CHUNK.getSwitch().trim()); - try { - int csize = Integer.parseInt(chunkSizeString); - if (csize < 0) { - csize = 0; - } - LOG.info("Set distcp blocksPerChunk to " + csize); - option.setBlocksPerChunk(csize); - } - catch (NumberFormatException e) { - throw new IllegalArgumentException("blocksPerChunk is invalid: " - + chunkSizeString, e); - } - } - } - /** * parseSizeLimit is a helper method for parsing the deprecated * argument SIZE_LIMIT. @@ -244,7 +211,8 @@ public class OptionsParser { DistCpOptionSwitch.FILE_LIMIT.getSwitch().trim()); try { Integer.parseInt(fileLimitString); - } catch (NumberFormatException e) { + } + catch (NumberFormatException e) { throw new IllegalArgumentException("File-limit is invalid: " + fileLimitString, e); } diff --git a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/SimpleCopyListing.java b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/SimpleCopyListing.java index af913474550..105e4f2fe17 100644 --- a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/SimpleCopyListing.java +++ b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/SimpleCopyListing.java @@ -19,7 +19,6 @@ package org.apache.hadoop.tools; import com.google.common.collect.Lists; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.fs.Path; @@ -48,7 +47,6 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Random; -import java.util.LinkedList; import static org.apache.hadoop.tools.DistCpConstants .HDFS_RESERVED_RAW_DIRECTORY_NAME; @@ -242,10 +240,10 @@ public class SimpleCopyListing extends CopyListing { final boolean preserveAcls = options.shouldPreserve(FileAttribute.ACL); final boolean preserveXAttrs = options.shouldPreserve(FileAttribute.XATTR); final boolean preserveRawXAttrs = options.shouldPreserveRawXattrs(); - LinkedList fileCopyListingStatus = + CopyListingFileStatus fileCopyListingStatus = DistCpUtils.toCopyListingFileStatus(sourceFS, fileStatus, - preserveAcls, preserveXAttrs, preserveRawXAttrs, - options.getBlocksPerChunk()); + preserveAcls, preserveXAttrs, preserveRawXAttrs); + writeToFileListingRoot(fileListWriter, fileCopyListingStatus, sourceRoot, options); } @@ -350,10 +348,9 @@ public class SimpleCopyListing extends CopyListing { FileStatus[] sourceFiles = sourceFS.listStatus(path); boolean explore = (sourceFiles != null && sourceFiles.length > 0); if (!explore || rootStatus.isDirectory()) { - LinkedList rootCopyListingStatus = - DistCpUtils.toCopyListingFileStatus(sourceFS, rootStatus, - preserveAcls, preserveXAttrs, preserveRawXAttrs, - options.getBlocksPerChunk()); + CopyListingFileStatus rootCopyListingStatus = + DistCpUtils.toCopyListingFileStatus(sourceFS, rootStatus, + preserveAcls, preserveXAttrs, preserveRawXAttrs); writeToFileListingRoot(fileListWriter, rootCopyListingStatus, sourcePathRoot, options); } @@ -363,20 +360,20 @@ public class SimpleCopyListing extends CopyListing { if (LOG.isDebugEnabled()) { LOG.debug("Recording source-path: " + sourceStatus.getPath() + " for copy."); } - LinkedList sourceCopyListingStatus = - DistCpUtils.toCopyListingFileStatus(sourceFS, sourceStatus, - preserveAcls && sourceStatus.isDirectory(), - preserveXAttrs && sourceStatus.isDirectory(), - preserveRawXAttrs && sourceStatus.isDirectory(), - options.getBlocksPerChunk()); - for (CopyListingFileStatus fs : sourceCopyListingStatus) { - if (randomizeFileListing) { - addToFileListing(statusList, - new FileStatusInfo(fs, sourcePathRoot), fileListWriter); - } else { - writeToFileListing(fileListWriter, fs, sourcePathRoot); - } + CopyListingFileStatus sourceCopyListingStatus = + DistCpUtils.toCopyListingFileStatus(sourceFS, sourceStatus, + preserveAcls && sourceStatus.isDirectory(), + preserveXAttrs && sourceStatus.isDirectory(), + preserveRawXAttrs && sourceStatus.isDirectory()); + if (randomizeFileListing) { + addToFileListing(statusList, + new FileStatusInfo(sourceCopyListingStatus, sourcePathRoot), + fileListWriter); + } else { + writeToFileListing(fileListWriter, sourceCopyListingStatus, + sourcePathRoot); } + if (sourceStatus.isDirectory()) { if (LOG.isDebugEnabled()) { LOG.debug("Adding source dir for traverse: " + sourceStatus.getPath()); @@ -644,20 +641,18 @@ public class SimpleCopyListing extends CopyListing { LOG.debug("Recording source-path: " + child.getPath() + " for copy."); } if (workResult.getSuccess()) { - LinkedList childCopyListingStatus = + CopyListingFileStatus childCopyListingStatus = DistCpUtils.toCopyListingFileStatus(sourceFS, child, preserveAcls && child.isDirectory(), preserveXAttrs && child.isDirectory(), - preserveRawXattrs && child.isDirectory(), - options.getBlocksPerChunk()); - - for (CopyListingFileStatus fs : childCopyListingStatus) { - if (randomizeFileListing) { - addToFileListing(fileStatuses, - new FileStatusInfo(fs, sourcePathRoot), fileListWriter); - } else { - writeToFileListing(fileListWriter, fs, sourcePathRoot); - } + preserveRawXattrs && child.isDirectory()); + if (randomizeFileListing) { + addToFileListing(fileStatuses, + new FileStatusInfo(childCopyListingStatus, sourcePathRoot), + fileListWriter); + } else { + writeToFileListing(fileListWriter, childCopyListingStatus, + sourcePathRoot); } } if (retry < maxRetries) { @@ -680,21 +675,19 @@ public class SimpleCopyListing extends CopyListing { } private void writeToFileListingRoot(SequenceFile.Writer fileListWriter, - LinkedList fileStatus, Path sourcePathRoot, + CopyListingFileStatus fileStatus, Path sourcePathRoot, DistCpOptions options) throws IOException { boolean syncOrOverwrite = options.shouldSyncFolder() || options.shouldOverwrite(); - for (CopyListingFileStatus fs : fileStatus) { - if (fs.getPath().equals(sourcePathRoot) && - fs.isDirectory() && syncOrOverwrite) { - // Skip the root-paths when syncOrOverwrite - if (LOG.isDebugEnabled()) { - LOG.debug("Skip " + fs.getPath()); - } - return; - } - writeToFileListing(fileListWriter, fs, sourcePathRoot); + if (fileStatus.getPath().equals(sourcePathRoot) && + fileStatus.isDirectory() && syncOrOverwrite) { + // Skip the root-paths when syncOrOverwrite + if (LOG.isDebugEnabled()) { + LOG.debug("Skip " + fileStatus.getPath()); + } + return; } + writeToFileListing(fileListWriter, fileStatus, sourcePathRoot); } private void writeToFileListing(SequenceFile.Writer fileListWriter, @@ -714,7 +707,7 @@ public class SimpleCopyListing extends CopyListing { fileListWriter.sync(); if (!fileStatus.isDirectory()) { - totalBytesToCopy += fileStatus.getSizeToCopy(); + totalBytesToCopy += fileStatus.getLen(); } else { totalDirs++; } diff --git a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/CopyCommitter.java b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/CopyCommitter.java index 6ddaab99c3d..75cefb488ae 100644 --- a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/CopyCommitter.java +++ b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/CopyCommitter.java @@ -27,7 +27,6 @@ import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.IOUtils; import org.apache.hadoop.io.SequenceFile; import org.apache.hadoop.io.Text; -import org.apache.hadoop.ipc.RemoteException; import org.apache.hadoop.mapreduce.JobContext; import org.apache.hadoop.mapreduce.JobStatus; import org.apache.hadoop.mapreduce.TaskAttemptContext; @@ -35,17 +34,14 @@ import org.apache.hadoop.mapreduce.lib.output.FileOutputCommitter; import org.apache.hadoop.tools.CopyListing; import org.apache.hadoop.tools.CopyListingFileStatus; import org.apache.hadoop.tools.DistCpConstants; -import org.apache.hadoop.tools.DistCpOptionSwitch; import org.apache.hadoop.tools.DistCpOptions; import org.apache.hadoop.tools.DistCpOptions.FileAttribute; import org.apache.hadoop.tools.GlobbedCopyListing; import org.apache.hadoop.tools.util.DistCpUtils; -import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; import java.util.EnumSet; -import java.util.LinkedList; import java.util.List; /** @@ -67,8 +63,7 @@ public class CopyCommitter extends FileOutputCommitter { private boolean syncFolder = false; private boolean overwrite = false; private boolean targetPathExists = true; - private boolean ignoreFailures = false; - + /** * Create a output committer * @@ -87,13 +82,8 @@ public class CopyCommitter extends FileOutputCommitter { Configuration conf = jobContext.getConfiguration(); syncFolder = conf.getBoolean(DistCpConstants.CONF_LABEL_SYNC_FOLDERS, false); overwrite = conf.getBoolean(DistCpConstants.CONF_LABEL_OVERWRITE, false); - targetPathExists = conf.getBoolean( - DistCpConstants.CONF_LABEL_TARGET_PATH_EXISTS, true); - ignoreFailures = conf.getBoolean( - DistCpOptionSwitch.IGNORE_FAILURES.getConfigLabel(), false); - - concatFileChunks(conf); - + targetPathExists = conf.getBoolean(DistCpConstants.CONF_LABEL_TARGET_PATH_EXISTS, true); + super.commitJob(jobContext); cleanupTempFiles(jobContext); @@ -179,112 +169,9 @@ public class CopyCommitter extends FileOutputCommitter { } } - private boolean isFileNotFoundException(IOException e) { - if (e instanceof FileNotFoundException) { - return true; - } - - if (e instanceof RemoteException) { - return ((RemoteException)e).unwrapRemoteException() - instanceof FileNotFoundException; - } - - return false; - } - - /** - * Concat chunk files for the same file into one. - * Iterate through copy listing, identify chunk files for the same file, - * concat them into one. - */ - private void concatFileChunks(Configuration conf) throws IOException { - - LOG.info("concat file chunks ..."); - - String spath = conf.get(DistCpConstants.CONF_LABEL_LISTING_FILE_PATH); - if (spath == null || spath.isEmpty()) { - return; - } - Path sourceListing = new Path(spath); - SequenceFile.Reader sourceReader = new SequenceFile.Reader(conf, - SequenceFile.Reader.file(sourceListing)); - Path targetRoot = - new Path(conf.get(DistCpConstants.CONF_LABEL_TARGET_WORK_PATH)); - - try { - CopyListingFileStatus srcFileStatus = new CopyListingFileStatus(); - Text srcRelPath = new Text(); - CopyListingFileStatus lastFileStatus = null; - LinkedList allChunkPaths = new LinkedList(); - - // Iterate over every source path that was copied. - while (sourceReader.next(srcRelPath, srcFileStatus)) { - if (srcFileStatus.isDirectory()) { - continue; - } - Path targetFile = new Path(targetRoot.toString() + "/" + srcRelPath); - Path targetFileChunkPath = - DistCpUtils.getSplitChunkPath(targetFile, srcFileStatus); - if (LOG.isDebugEnabled()) { - LOG.debug(" add " + targetFileChunkPath + " to concat."); - } - allChunkPaths.add(targetFileChunkPath); - if (srcFileStatus.getChunkOffset() + srcFileStatus.getChunkLength() - == srcFileStatus.getLen()) { - // This is the last chunk of the splits, consolidate allChunkPaths - try { - concatFileChunks(conf, targetFile, allChunkPaths); - } catch (IOException e) { - // If the concat failed because a chunk file doesn't exist, - // then we assume that the CopyMapper has skipped copying this - // file, and we ignore the exception here. - // If a chunk file should have been created but it was not, then - // the CopyMapper would have failed. - if (!isFileNotFoundException(e)) { - String emsg = "Failed to concat chunk files for " + targetFile; - if (!ignoreFailures) { - throw new IOException(emsg, e); - } else { - LOG.warn(emsg, e); - } - } - } - allChunkPaths.clear(); - lastFileStatus = null; - } else { - if (lastFileStatus == null) { - lastFileStatus = new CopyListingFileStatus(srcFileStatus); - } else { - // Two neighboring chunks have to be consecutive ones for the same - // file, for them to be merged - if (!srcFileStatus.getPath().equals(lastFileStatus.getPath()) || - srcFileStatus.getChunkOffset() != - (lastFileStatus.getChunkOffset() + - lastFileStatus.getChunkLength())) { - String emsg = "Inconsistent sequence file: current " + - "chunk file " + srcFileStatus + " doesnt match prior " + - "entry " + lastFileStatus; - if (!ignoreFailures) { - throw new IOException(emsg); - } else { - LOG.warn(emsg + ", skipping concat this set."); - } - } else { - lastFileStatus.setChunkOffset(srcFileStatus.getChunkOffset()); - lastFileStatus.setChunkLength(srcFileStatus.getChunkLength()); - } - } - } - } - } finally { - IOUtils.closeStream(sourceReader); - } - } - // This method changes the target-directories' file-attributes (owner, // user/group permissions, etc.) based on the corresponding source directories. - private void preserveFileAttributesForDirectories(Configuration conf) - throws IOException { + private void preserveFileAttributesForDirectories(Configuration conf) throws IOException { String attrSymbols = conf.get(DistCpConstants.CONF_LABEL_PRESERVE_STATUS); final boolean syncOrOverwrite = syncFolder || overwrite; @@ -438,57 +325,4 @@ public class CopyCommitter extends FileOutputCommitter { ", Unable to move to " + finalDir); } } - - /** - * Concat the passed chunk files into one and rename it the targetFile. - */ - private void concatFileChunks(Configuration conf, Path targetFile, - LinkedList allChunkPaths) throws IOException { - if (allChunkPaths.size() == 1) { - return; - } - if (LOG.isDebugEnabled()) { - LOG.debug("concat " + targetFile + " allChunkSize+ " - + allChunkPaths.size()); - } - FileSystem dstfs = targetFile.getFileSystem(conf); - - Path firstChunkFile = allChunkPaths.removeFirst(); - Path[] restChunkFiles = new Path[allChunkPaths.size()]; - allChunkPaths.toArray(restChunkFiles); - if (LOG.isDebugEnabled()) { - LOG.debug("concat: firstchunk: " + dstfs.getFileStatus(firstChunkFile)); - int i = 0; - for (Path f : restChunkFiles) { - LOG.debug("concat: other chunk: " + i + ": " + dstfs.getFileStatus(f)); - ++i; - } - } - dstfs.concat(firstChunkFile, restChunkFiles); - if (LOG.isDebugEnabled()) { - LOG.debug("concat: result: " + dstfs.getFileStatus(firstChunkFile)); - } - rename(dstfs, firstChunkFile, targetFile); - } - - /** - * Rename tmp to dst on destFileSys. - * @param destFileSys the file ssystem - * @param tmp the source path - * @param dst the destination path - * @throws IOException if renaming failed - */ - private static void rename(FileSystem destFileSys, Path tmp, Path dst) - throws IOException { - try { - if (destFileSys.exists(dst)) { - destFileSys.delete(dst, true); - } - destFileSys.rename(tmp, dst); - } catch (IOException ioe) { - throw new IOException("Fail to rename tmp file (=" + tmp - + ") to destination file (=" + dst + ")", ioe); - } - } - } diff --git a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/CopyMapper.java b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/CopyMapper.java index d6b3ba817b0..e1873f17e41 100644 --- a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/CopyMapper.java +++ b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/CopyMapper.java @@ -156,12 +156,10 @@ public class CopyMapper extends Mapper sourceFS = sourcePath.getFileSystem(conf); final boolean preserveXAttrs = fileAttributes.contains(FileAttribute.XATTR); - sourceCurrStatus = DistCpUtils.toCopyListingFileStatusHelper(sourceFS, - sourceFS.getFileStatus(sourcePath), - fileAttributes.contains(FileAttribute.ACL), - preserveXAttrs, preserveRawXattrs, - sourceFileStatus.getChunkOffset(), - sourceFileStatus.getChunkLength()); + sourceCurrStatus = DistCpUtils.toCopyListingFileStatus(sourceFS, + sourceFS.getFileStatus(sourcePath), + fileAttributes.contains(FileAttribute.ACL), + preserveXAttrs, preserveRawXattrs); } catch (FileNotFoundException e) { throw new IOException(new RetriableFileCopyCommand.CopyReadException(e)); } @@ -175,8 +173,7 @@ public class CopyMapper extends Mapper LOG.debug("Path could not be found: " + target, ignore); } - if (targetStatus != null && - (targetStatus.isDirectory() != sourceCurrStatus.isDirectory())) { + if (targetStatus != null && (targetStatus.isDirectory() != sourceCurrStatus.isDirectory())) { throw new IOException("Can't replace " + target + ". Target is " + getFileType(targetStatus) + ", Source is " + getFileType(sourceCurrStatus)); } @@ -186,28 +183,19 @@ public class CopyMapper extends Mapper return; } - FileAction action = checkUpdate(sourceFS, sourceCurrStatus, target, - targetStatus); - - Path tmpTarget = target; + FileAction action = checkUpdate(sourceFS, sourceCurrStatus, target, targetStatus); if (action == FileAction.SKIP) { LOG.info("Skipping copy of " + sourceCurrStatus.getPath() + " to " + target); updateSkipCounters(context, sourceCurrStatus); context.write(null, new Text("SKIP: " + sourceCurrStatus.getPath())); - } else { - if (sourceCurrStatus.isSplit()) { - tmpTarget = DistCpUtils.getSplitChunkPath(target, sourceCurrStatus); - } - if (LOG.isDebugEnabled()) { - LOG.debug("copying " + sourceCurrStatus + " " + tmpTarget); - } - copyFileWithRetry(description, sourceCurrStatus, tmpTarget, context, + copyFileWithRetry(description, sourceCurrStatus, target, context, action, fileAttributes); } - DistCpUtils.preserve(target.getFileSystem(conf), tmpTarget, - sourceCurrStatus, fileAttributes, preserveRawXattrs); + + DistCpUtils.preserve(target.getFileSystem(conf), target, sourceCurrStatus, + fileAttributes, preserveRawXattrs); } catch (IOException exception) { handleFailures(exception, sourceFileStatus, target, context); } @@ -273,12 +261,8 @@ public class CopyMapper extends Mapper private void handleFailures(IOException exception, CopyListingFileStatus sourceFileStatus, Path target, Context context) throws IOException, InterruptedException { - LOG.error("Failure in copying " + sourceFileStatus.getPath() + - (sourceFileStatus.isSplit()? "," - + " offset=" + sourceFileStatus.getChunkOffset() - + " chunkLength=" + sourceFileStatus.getChunkLength() - : "") + - " to " + target, exception); + LOG.error("Failure in copying " + sourceFileStatus.getPath() + " to " + + target, exception); if (ignoreFailures && ExceptionUtils.indexOfType(exception, CopyReadException.class) != -1) { diff --git a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/RetriableFileCopyCommand.java b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/RetriableFileCopyCommand.java index 2c17fef1a38..06acd78a8a1 100644 --- a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/RetriableFileCopyCommand.java +++ b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/RetriableFileCopyCommand.java @@ -118,21 +118,17 @@ public class RetriableFileCopyCommand extends RetriableCommand { .contains(FileAttribute.CHECKSUMTYPE) ? sourceFS .getFileChecksum(sourcePath) : null; - long offset = (action == FileAction.APPEND) ? - targetFS.getFileStatus(target).getLen() : source.getChunkOffset(); + final long offset = action == FileAction.APPEND ? targetFS.getFileStatus( + target).getLen() : 0; long bytesRead = copyToFile(targetPath, targetFS, source, offset, context, fileAttributes, sourceChecksum); - if (!source.isSplit()) { - compareFileLengths(source, targetPath, configuration, bytesRead - + offset); - } + compareFileLengths(source, targetPath, configuration, bytesRead + + offset); //At this point, src&dest lengths are same. if length==0, we skip checksum if ((bytesRead != 0) && (!skipCrc)) { - if (!source.isSplit()) { - compareCheckSums(sourceFS, source.getPath(), sourceChecksum, - targetFS, targetPath); - } + compareCheckSums(sourceFS, source.getPath(), sourceChecksum, + targetFS, targetPath); } // it's not append case, thus we first write to a temporary file, rename // it to the target path. @@ -253,26 +249,16 @@ public class RetriableFileCopyCommand extends RetriableCommand { ThrottledInputStream inStream = null; long totalBytesRead = 0; - long chunkLength = source2.getChunkLength(); - boolean finished = false; try { inStream = getInputStream(source, context.getConfiguration()); int bytesRead = readBytes(inStream, buf, sourceOffset); while (bytesRead >= 0) { - if (chunkLength > 0 && - (totalBytesRead + bytesRead) >= chunkLength) { - bytesRead = (int)(chunkLength - totalBytesRead); - finished = true; - } totalBytesRead += bytesRead; if (action == FileAction.APPEND) { sourceOffset += bytesRead; } outStream.write(buf, 0, bytesRead); updateContextStatus(totalBytesRead, context, source2); - if (finished) { - break; - } bytesRead = readBytes(inStream, buf, sourceOffset); } outStream.close(); diff --git a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/UniformSizeInputFormat.java b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/UniformSizeInputFormat.java index d1c18ea8d16..3e86d0931bc 100644 --- a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/UniformSizeInputFormat.java +++ b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/UniformSizeInputFormat.java @@ -99,8 +99,7 @@ public class UniformSizeInputFormat while (reader.next(srcRelPath, srcFileStatus)) { // If adding the current file would cause the bytes per map to exceed // limit. Add the current file to new split - if (currentSplitSize + srcFileStatus.getChunkLength() > nBytesPerSplit - && lastPosition != 0) { + if (currentSplitSize + srcFileStatus.getLen() > nBytesPerSplit && lastPosition != 0) { FileSplit split = new FileSplit(listingFilePath, lastSplitStart, lastPosition - lastSplitStart, null); if (LOG.isDebugEnabled()) { @@ -110,7 +109,7 @@ public class UniformSizeInputFormat lastSplitStart = lastPosition; currentSplitSize = 0; } - currentSplitSize += srcFileStatus.getChunkLength(); + currentSplitSize += srcFileStatus.getLen(); lastPosition = reader.getPosition(); } if (lastPosition > lastSplitStart) { diff --git a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/util/DistCpUtils.java b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/util/DistCpUtils.java index e315b848de4..76bc4c56268 100644 --- a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/util/DistCpUtils.java +++ b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/util/DistCpUtils.java @@ -19,11 +19,9 @@ package org.apache.hadoop.tools.util; import com.google.common.collect.Maps; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; -import org.apache.hadoop.fs.BlockLocation; import org.apache.hadoop.fs.FileChecksum; import org.apache.hadoop.fs.FileStatus; import org.apache.hadoop.fs.FileSystem; @@ -32,7 +30,6 @@ import org.apache.hadoop.fs.XAttr; import org.apache.hadoop.fs.permission.AclEntry; import org.apache.hadoop.fs.permission.AclUtil; import org.apache.hadoop.fs.permission.FsPermission; -import org.apache.hadoop.hdfs.DistributedFileSystem; import org.apache.hadoop.io.SequenceFile; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.InputFormat; @@ -47,7 +44,6 @@ import org.apache.hadoop.util.StringUtils; import java.io.IOException; import java.text.DecimalFormat; import java.util.EnumSet; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -120,7 +116,7 @@ public class DistCpUtils { * @return Class implementing the strategy specified in options. */ public static Class getStrategy(Configuration conf, - DistCpOptions options) { + DistCpOptions options) { String confLabel = "distcp." + StringUtils.toLowerCase(options.getCopyStrategy()) + ".strategy" + ".impl"; @@ -301,86 +297,6 @@ public class DistCpUtils { return fileSystem.getXAttrs(path); } - /** - * Converts FileStatus to a list of CopyListingFileStatus. - * The resulted list contains either one CopyListingFileStatus per chunk of - * file-blocks (if file-size exceeds blockSize * blocksPerChunk, and there - * are more blocks in the file than blocksperChunk), or a single - * CopyListingFileStatus for the entire file (if file-size is too small to - * split). - * If preserving ACLs, populates the CopyListingFileStatus with the ACLs. - * If preserving XAttrs, populates the CopyListingFileStatus with the XAttrs. - * - * @param fileSystem FileSystem containing the file - * @param fileStatus FileStatus of file - * @param preserveAcls boolean true if preserving ACLs - * @param preserveXAttrs boolean true if preserving XAttrs - * @param preserveRawXAttrs boolean true if preserving raw.* XAttrs - * @param blocksPerChunk size of chunks when copying chunks in parallel - * @return list of CopyListingFileStatus - * @throws IOException if there is an I/O error - */ - public static LinkedList toCopyListingFileStatus( - FileSystem fileSystem, FileStatus fileStatus, boolean preserveAcls, - boolean preserveXAttrs, boolean preserveRawXAttrs, int blocksPerChunk) - throws IOException { - LinkedList copyListingFileStatus = - new LinkedList(); - - final CopyListingFileStatus clfs = toCopyListingFileStatusHelper( - fileSystem, fileStatus, preserveAcls, - preserveXAttrs, preserveRawXAttrs, - 0, fileStatus.getLen()); - final long blockSize = fileStatus.getBlockSize(); - if (LOG.isDebugEnabled()) { - LOG.debug("toCopyListing: " + fileStatus + " chunkSize: " - + blocksPerChunk + " isDFS: " + - (fileSystem instanceof DistributedFileSystem)); - } - if ((blocksPerChunk > 0) && - !fileStatus.isDirectory() && - (fileStatus.getLen() > blockSize * blocksPerChunk)) { - // split only when the file size is larger than the intended chunk size - final BlockLocation[] blockLocations; - blockLocations = fileSystem.getFileBlockLocations(fileStatus, 0, - fileStatus.getLen()); - - int numBlocks = blockLocations.length; - long curPos = 0; - if (numBlocks <= blocksPerChunk) { - if (LOG.isDebugEnabled()) { - LOG.debug(" add file " + clfs); - } - copyListingFileStatus.add(clfs); - } else { - int i = 0; - while (i < numBlocks) { - long curLength = 0; - for (int j = 0; j < blocksPerChunk && i < numBlocks; ++j, ++i) { - curLength += blockLocations[i].getLength(); - } - if (curLength > 0) { - CopyListingFileStatus clfs1 = new CopyListingFileStatus(clfs); - clfs1.setChunkOffset(curPos); - clfs1.setChunkLength(curLength); - if (LOG.isDebugEnabled()) { - LOG.debug(" add file chunk " + clfs1); - } - copyListingFileStatus.add(clfs1); - curPos += curLength; - } - } - } - } else { - if (LOG.isDebugEnabled()) { - LOG.debug(" add file/dir " + clfs); - } - copyListingFileStatus.add(clfs); - } - - return copyListingFileStatus; - } - /** * Converts a FileStatus to a CopyListingFileStatus. If preserving ACLs, * populates the CopyListingFileStatus with the ACLs. If preserving XAttrs, @@ -391,17 +307,13 @@ public class DistCpUtils { * @param preserveAcls boolean true if preserving ACLs * @param preserveXAttrs boolean true if preserving XAttrs * @param preserveRawXAttrs boolean true if preserving raw.* XAttrs - * @param chunkOffset chunk offset in bytes - * @param chunkLength chunk length in bytes - * @return CopyListingFileStatus * @throws IOException if there is an I/O error */ - public static CopyListingFileStatus toCopyListingFileStatusHelper( + public static CopyListingFileStatus toCopyListingFileStatus( FileSystem fileSystem, FileStatus fileStatus, boolean preserveAcls, - boolean preserveXAttrs, boolean preserveRawXAttrs, - long chunkOffset, long chunkLength) throws IOException { + boolean preserveXAttrs, boolean preserveRawXAttrs) throws IOException { CopyListingFileStatus copyListingFileStatus = - new CopyListingFileStatus(fileStatus, chunkOffset, chunkLength); + new CopyListingFileStatus(fileStatus); if (preserveAcls) { FsPermission perm = fileStatus.getPermission(); if (perm.getAclBit()) { @@ -558,19 +470,4 @@ public class DistCpUtils { return (sourceChecksum == null || targetChecksum == null || sourceChecksum.equals(targetChecksum)); } - - /* - * Return the Path for a given chunk. - * Used when splitting large file into chunks to copy in parallel. - * @param targetFile path to target file - * @param srcFileStatus source file status in copy listing - * @return path to the chunk specified by the parameters to store - * in target cluster temporarily - */ - public static Path getSplitChunkPath(Path targetFile, - CopyListingFileStatus srcFileStatus) { - return new Path(targetFile.toString() - + ".____distcpSplit____" + srcFileStatus.getChunkOffset() - + "." + srcFileStatus.getChunkLength()); - } } diff --git a/hadoop-tools/hadoop-distcp/src/site/markdown/DistCp.md.vm b/hadoop-tools/hadoop-distcp/src/site/markdown/DistCp.md.vm index a77deb2ffee..41a6e94aeee 100644 --- a/hadoop-tools/hadoop-distcp/src/site/markdown/DistCp.md.vm +++ b/hadoop-tools/hadoop-distcp/src/site/markdown/DistCp.md.vm @@ -237,7 +237,6 @@ Flag | Description | Notes `-rdiff ` | Use snapshot diff report between given two snapshots to identify what has been changed on the target since the snapshot `` was created on the target, and apply the diff reversely to the target, and copy modified files from the source's ``, to make the target the same as ``. | This option is valid only with `-update` option and the following conditions should be satisfied.
    1. Both the source and the target FileSystem must be DistributedFileSystem. The source and the target can be two different clusters/paths, or they can be exactly the same cluster/path. In the latter case, modified files are copied from target's `` to target's current state).
    2. Two snapshots `` and `` have been created on the target FS, and `` is older than ``. No change has been made on target since `` was created on the target.
    3. The source has the same snapshot ``, which has the same content as the `` on the target. All the files/directories in the target's `` are the same with source's ``.
    | `-numListstatusThreads` | Number of threads to use for building file listing | At most 40 threads. `-skipcrccheck` | Whether to skip CRC checks between source and target paths. | -`-blocksperchunk ` | Number of blocks per chunk. When specified, split files into chunks to copy in parallel | If set to a positive value, files with more blocks than this value will be split into chunks of `` blocks to be transferred in parallel, and reassembled on the destination. By default, `` is 0 and the files will be transmitted in their entirety without splitting. This switch is only applicable when the source file system implements getBlockLocations method and the target file system implements concat method. | Architecture of DistCp ---------------------- diff --git a/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestDistCpSystem.java b/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestDistCpSystem.java index b2266b3344d..e3018a07730 100644 --- a/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestDistCpSystem.java +++ b/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestDistCpSystem.java @@ -23,27 +23,17 @@ import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.PrintStream; +import java.io.OutputStream; import java.net.URI; import java.util.ArrayList; import java.util.List; -import java.util.Random; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; -import org.apache.hadoop.fs.FSDataInputStream; import org.apache.hadoop.fs.FileStatus; import org.apache.hadoop.fs.FileSystem; -import org.apache.hadoop.fs.FsShell; import org.apache.hadoop.fs.Path; -import org.apache.hadoop.hdfs.DFSConfigKeys; -import org.apache.hadoop.hdfs.DFSTestUtil; -import org.apache.hadoop.hdfs.DistributedFileSystem; import org.apache.hadoop.hdfs.MiniDFSCluster; -import org.apache.hadoop.hdfs.client.HdfsClientConfigKeys; import org.apache.hadoop.util.ToolRunner; import org.junit.AfterClass; import org.junit.Assert; @@ -57,15 +47,11 @@ import org.junit.rules.Timeout; */ public class TestDistCpSystem { - private static final Log LOG = - LogFactory.getLog(TestDistCpSystem.class); - @Rule public Timeout globalTimeout = new Timeout(30000); private static final String SRCDAT = "srcdat"; private static final String DSTDAT = "dstdat"; - private static final long BLOCK_SIZE = 1024; private static MiniDFSCluster cluster; private static Configuration conf; @@ -77,76 +63,27 @@ public class TestDistCpSystem { this.path = path; this.isDir = isDir; } - - String getPath() { - return path; - } - - boolean isDirectory() { - return isDir; - } - } - - @BeforeClass - public static void beforeClass() throws IOException { - conf = new Configuration(); - conf.setLong(DFSConfigKeys.DFS_NAMENODE_MIN_BLOCK_SIZE_KEY, BLOCK_SIZE); - conf.setLong(DFSConfigKeys.DFS_BLOCK_SIZE_KEY, BLOCK_SIZE); - cluster = new MiniDFSCluster.Builder(conf).numDataNodes(2).build(); - cluster.waitActive(); - } - - @AfterClass - public static void afterClass() throws IOException { - if (cluster != null) { - cluster.shutdown(); - } - } - - static String execCmd(FsShell shell, String... args) throws Exception { - ByteArrayOutputStream baout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(baout, true); - PrintStream old = System.out; - System.setOut(out); - shell.run(args); - out.close(); - System.setOut(old); - return baout.toString(); + String getPath() { return path; } + boolean isDirectory() { return isDir; } } - private void createFiles(DistributedFileSystem fs, String topdir, - FileEntry[] entries, long chunkSize) throws IOException { - long seed = System.currentTimeMillis(); - Random rand = new Random(seed); - short replicationFactor = 2; + private void createFiles(FileSystem fs, String topdir, + FileEntry[] entries) throws IOException { for (FileEntry entry : entries) { - Path newPath = new Path(topdir + "/" + entry.getPath()); + Path newpath = new Path(topdir + "/" + entry.getPath()); if (entry.isDirectory()) { - fs.mkdirs(newPath); + fs.mkdirs(newpath); } else { - long fileSize = BLOCK_SIZE *100; - int bufSize = 128; - if (chunkSize == -1) { - DFSTestUtil.createFile(fs, newPath, bufSize, - fileSize, BLOCK_SIZE, replicationFactor, seed); - } else { - // Create a variable length block file, by creating - // one block of half block size at the chunk boundary - long seg1 = chunkSize * BLOCK_SIZE - BLOCK_SIZE / 2; - long seg2 = fileSize - seg1; - DFSTestUtil.createFile(fs, newPath, bufSize, - seg1, BLOCK_SIZE, replicationFactor, seed); - DFSTestUtil.appendFileNewBlock(fs, newPath, (int)seg2); + OutputStream out = fs.create(newpath); + try { + out.write((topdir + "/" + entry).getBytes()); + out.write("\n".getBytes()); + } finally { + out.close(); } } - seed = System.currentTimeMillis() + rand.nextLong(); } } - - private void createFiles(DistributedFileSystem fs, String topdir, - FileEntry[] entries) throws IOException { - createFiles(fs, topdir, entries, -1); - } private static FileStatus[] getFileStatus(FileSystem fs, String topdir, FileEntry[] files) throws IOException { @@ -167,19 +104,18 @@ public class TestDistCpSystem { } private void testPreserveUserHelper(String testRoot, - FileEntry[] srcEntries, - FileEntry[] dstEntries, - boolean createSrcDir, - boolean createTgtDir, - boolean update) throws Exception { + FileEntry[] srcEntries, + FileEntry[] dstEntries, + boolean createSrcDir, + boolean createTgtDir, + boolean update) throws Exception { final String testSrcRel = SRCDAT; final String testSrc = testRoot + "/" + testSrcRel; final String testDstRel = DSTDAT; final String testDst = testRoot + "/" + testDstRel; String nnUri = FileSystem.getDefaultUri(conf).toString(); - DistributedFileSystem fs = (DistributedFileSystem) - FileSystem.get(URI.create(nnUri), conf); + FileSystem fs = FileSystem.get(URI.create(nnUri), conf); fs.mkdirs(new Path(testRoot)); if (createSrcDir) { fs.mkdirs(new Path(testSrc)); @@ -193,8 +129,8 @@ public class TestDistCpSystem { for(int i = 0; i < srcEntries.length; i++) { fs.setOwner(srcstats[i].getPath(), "u" + i, null); } - String[] args = update? new String[]{"-pub", "-update", nnUri+testSrc, - nnUri+testDst} : new String[]{"-pub", nnUri+testSrc, nnUri+testDst}; + String[] args = update? new String[]{"-pu", "-update", nnUri+testSrc, + nnUri+testDst} : new String[]{"-pu", nnUri+testSrc, nnUri+testDst}; ToolRunner.run(conf, new DistCp(), args); @@ -209,263 +145,20 @@ public class TestDistCpSystem { deldir(fs, testRoot); } - private void compareFiles(FileSystem fs, FileStatus srcStat, - FileStatus dstStat) throws Exception { - LOG.info("Comparing " + srcStat + " and " + dstStat); - assertEquals(srcStat.isDirectory(), dstStat.isDirectory()); - assertEquals(srcStat.getReplication(), dstStat.getReplication()); - assertEquals("File POSIX permission should match", - srcStat.getPermission(), dstStat.getPermission()); - assertEquals("File user ownership should match", - srcStat.getOwner(), dstStat.getOwner()); - assertEquals("File group ownership should match", - srcStat.getGroup(), dstStat.getGroup()); - // TODO; check ACL attributes - - if (srcStat.isDirectory()) { - return; - } - - assertEquals("File length should match (" + srcStat.getPath() + ")", - srcStat.getLen(), dstStat.getLen()); - - FSDataInputStream srcIn = fs.open(srcStat.getPath()); - FSDataInputStream dstIn = fs.open(dstStat.getPath()); - try { - byte[] readSrc = new byte[(int) - HdfsClientConfigKeys.DFS_BLOCK_SIZE_DEFAULT]; - byte[] readDst = new byte[(int) - HdfsClientConfigKeys.DFS_BLOCK_SIZE_DEFAULT]; - - int srcBytesRead = 0, tgtBytesRead = 0; - int srcIdx = 0, tgtIdx = 0; - long totalComparedBytes = 0; - while (true) { - if (srcBytesRead == 0) { - srcBytesRead = srcIn.read(readSrc); - srcIdx = 0; - } - if (tgtBytesRead == 0) { - tgtBytesRead = dstIn.read(readDst); - tgtIdx = 0; - } - if (srcBytesRead == 0 || tgtBytesRead == 0) { - LOG.info("______ compared src and dst files for " - + totalComparedBytes + " bytes, content match."); - if (srcBytesRead != tgtBytesRead) { - Assert.fail("Read mismatching size, compared " - + totalComparedBytes + " bytes between src and dst file " - + srcStat + " and " + dstStat); - } - if (totalComparedBytes != srcStat.getLen()) { - Assert.fail("Only read/compared " + totalComparedBytes + - " bytes between src and dst file " + srcStat + - " and " + dstStat); - } else { - // success - break; - } - } - for (; srcIdx < srcBytesRead && tgtIdx < tgtBytesRead; - ++srcIdx, ++tgtIdx) { - if (readSrc[srcIdx] != readDst[tgtIdx]) { - Assert.fail("src and dst file does not match at " - + totalComparedBytes + " between " - + srcStat + " and " + dstStat); - } - ++totalComparedBytes; - } - LOG.info("______ compared src and dst files for " - + totalComparedBytes + " bytes, content match. FileLength: " - + srcStat.getLen()); - if (totalComparedBytes == srcStat.getLen()) { - LOG.info("______ Final:" + srcIdx + " " - + srcBytesRead + " " + tgtIdx + " " + tgtBytesRead); - break; - } - if (srcIdx == srcBytesRead) { - srcBytesRead = 0; - } - if (tgtIdx == tgtBytesRead) { - tgtBytesRead = 0; - } - } - } finally { - if (srcIn != null) { - srcIn.close(); - } - if (dstIn != null) { - dstIn.close(); - } - } + @BeforeClass + public static void beforeClass() throws IOException { + conf = new Configuration(); + cluster = new MiniDFSCluster.Builder(conf).numDataNodes(2).build(); + cluster.waitActive(); } - // WC: needed because the current distcp does not create target dirs - private void createDestDir(FileSystem fs, String testDst, - FileStatus[] srcStats, FileEntry[] srcFiles) throws IOException { - fs.mkdirs(new Path(testDst)); - - for (int i=0; i=0; --i) { - if (!srcFiles[i].isDirectory()) { - LOG.info("Modifying " + srcStats[i].getPath()); - DFSTestUtil.appendFileNewBlock(fs, srcStats[i].getPath(), - (int)BLOCK_SIZE * 3); - break; - } - } - // get file status after modifying file - srcStats = getFileStatus(fs, testRoot, srcFiles); - - args = new String[] {"-pugp", "-update", "-blocksperchunk", - String.valueOf(chunkSize), - nnUri + testSrc, nnUri + testDst + "/" + testSrcRel}; - - copyAndVerify(fs, srcFiles, srcStats, testDst, args); - - deldir(fs, testRoot); - } - - @Test - public void testRecursiveChunkCopy() throws Exception { - FileEntry[] srcFiles = { - new FileEntry(SRCDAT, true), - new FileEntry(SRCDAT + "/file0", false), - new FileEntry(SRCDAT + "/dir1", true), - new FileEntry(SRCDAT + "/dir2", true), - new FileEntry(SRCDAT + "/dir1/file1", false) - }; - chunkCopy(srcFiles); - } - - @Test - public void testChunkCopyOneFile() throws Exception { - FileEntry[] srcFiles = { - new FileEntry(SRCDAT, true), - new FileEntry(SRCDAT + "/file0", false) - }; - chunkCopy(srcFiles); - } - - @Test - public void testDistcpLargeFile() throws Exception { - FileEntry[] srcfiles = { - new FileEntry(SRCDAT, true), - new FileEntry(SRCDAT + "/file", false) - }; - - final String testRoot = "/testdir"; - final String testSrcRel = SRCDAT; - final String testSrc = testRoot + "/" + testSrcRel; - final String testDstRel = DSTDAT; - final String testDst = testRoot + "/" + testDstRel; - - String nnUri = FileSystem.getDefaultUri(conf).toString(); - DistributedFileSystem fs = - (DistributedFileSystem) FileSystem.get(URI.create(nnUri), conf); - fs.mkdirs(new Path(testRoot)); - fs.mkdirs(new Path(testSrc)); - fs.mkdirs(new Path(testDst)); - long chunkSize = 6; - createFiles(fs, testRoot, srcfiles, chunkSize); - - String srcFileName = testRoot + Path.SEPARATOR + srcfiles[1].getPath(); - Path srcfile = new Path(srcFileName); - - if(!cluster.getFileSystem().exists(srcfile)){ - throw new Exception("src not exist"); - } - - final long srcLen = fs.getFileStatus(srcfile).getLen(); - - FileStatus[] srcstats = getFileStatus(fs, testRoot, srcfiles); - for (int i = 0; i < srcfiles.length; i++) { - fs.setOwner(srcstats[i].getPath(), "u" + i, null); - } - String[] args = new String[] { - "-blocksperchunk", - String.valueOf(chunkSize), - nnUri + testSrc, - nnUri + testDst - }; - - LOG.info("_____ running distcp: " + args[0] + " " + args[1]); - ToolRunner.run(conf, new DistCp(), args); - - String realTgtPath = testDst; - FileStatus[] dststat = getFileStatus(fs, realTgtPath, srcfiles); - assertEquals("File length should match", srcLen, - dststat[dststat.length - 1].getLen()); - - this.compareFiles(fs, srcstats[srcstats.length-1], - dststat[dststat.length-1]); - deldir(fs, testRoot); - } - @Test public void testPreserveUseNonEmptyDir() throws Exception { String testRoot = "/testdir." + getMethodName(); @@ -487,6 +180,7 @@ public class TestDistCpSystem { testPreserveUserHelper(testRoot, srcfiles, dstfiles, false, false, false); } + @Test public void testPreserveUserEmptyDir() throws Exception { String testRoot = "/testdir." + getMethodName(); diff --git a/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestOptionsParser.java b/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestOptionsParser.java index f94ba97ec34..efe46272e38 100644 --- a/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestOptionsParser.java +++ b/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestOptionsParser.java @@ -394,7 +394,7 @@ public class TestOptionsParser { + "copyStrategy='uniformsize', preserveStatus=[], " + "preserveRawXattrs=false, atomicWorkPath=null, logPath=null, " + "sourceFileListing=abc, sourcePaths=null, targetPath=xyz, " - + "targetPathExists=true, filtersFile='null', blocksPerChunk=0}"; + + "targetPathExists=true, filtersFile='null'}"; String optionString = option.toString(); Assert.assertEquals(val, optionString); Assert.assertNotSame(DistCpOptionSwitch.ATOMIC_COMMIT.toString(), diff --git a/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/mapred/TestCopyCommitter.java b/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/mapred/TestCopyCommitter.java index 2452d6fee3d..2e9a350b1ca 100644 --- a/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/mapred/TestCopyCommitter.java +++ b/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/mapred/TestCopyCommitter.java @@ -81,10 +81,6 @@ public class TestCopyCommitter { @Before public void createMetaFolder() { config.set(DistCpConstants.CONF_LABEL_META_FOLDER, "/meta"); - // Unset listing file path since the config is shared by - // multiple tests, and some test doesn't set it, such as - // testNoCommitAction, but the distcp code will check it. - config.set(DistCpConstants.CONF_LABEL_LISTING_FILE_PATH, ""); Path meta = new Path("/meta"); try { cluster.getFileSystem().mkdirs(meta); @@ -330,6 +326,7 @@ public class TestCopyCommitter { committer.commitJob(jobContext); Assert.assertFalse(fs.exists(new Path(workPath))); Assert.assertTrue(fs.exists(new Path(finalPath))); + } catch (IOException e) { LOG.error("Exception encountered while testing for preserve status", e); Assert.fail("Atomic commit failure"); From bf3fb585aaf2b179836e139c041fc87920a3c886 Mon Sep 17 00:00:00 2001 From: Yongjun Zhang Date: Thu, 30 Mar 2017 17:01:15 -0700 Subject: [PATCH 173/188] HADOOP-11794. Enable distcp to copy blocks in parallel. Contributed by Yongjun Zhang, Wei-Chiu Chuang, Xiao Chen, Rosie Li. --- .../org/apache/hadoop/hdfs/DFSTestUtil.java | 22 +- .../org/apache/hadoop/tools/CopyListing.java | 37 +- .../hadoop/tools/CopyListingFileStatus.java | 87 ++++- .../java/org/apache/hadoop/tools/DistCp.java | 52 +++ .../hadoop/tools/DistCpOptionSwitch.java | 10 + .../apache/hadoop/tools/DistCpOptions.java | 22 +- .../apache/hadoop/tools/OptionsParser.java | 36 +- .../hadoop/tools/SimpleCopyListing.java | 83 ++-- .../hadoop/tools/mapred/CopyCommitter.java | 174 ++++++++- .../hadoop/tools/mapred/CopyMapper.java | 40 +- .../mapred/RetriableFileCopyCommand.java | 26 +- .../tools/mapred/UniformSizeInputFormat.java | 5 +- .../apache/hadoop/tools/util/DistCpUtils.java | 111 +++++- .../src/site/markdown/DistCp.md.vm | 1 + .../apache/hadoop/tools/TestDistCpSystem.java | 368 ++++++++++++++++-- .../hadoop/tools/TestOptionsParser.java | 2 +- .../tools/mapred/TestCopyCommitter.java | 5 +- 17 files changed, 971 insertions(+), 110 deletions(-) diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/DFSTestUtil.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/DFSTestUtil.java index 13291952a9e..9b782f32f2a 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/DFSTestUtil.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/DFSTestUtil.java @@ -862,7 +862,27 @@ public class DFSTestUtil { out.write(toAppend); } } - + + /** + * Append specified length of bytes to a given file, starting with new block. + * @param fs The file system + * @param p Path of the file to append + * @param length Length of bytes to append to the file + * @throws IOException + */ + public static void appendFileNewBlock(DistributedFileSystem fs, + Path p, int length) throws IOException { + assert fs.exists(p); + assert length >= 0; + byte[] toAppend = new byte[length]; + Random random = new Random(); + random.nextBytes(toAppend); + try (FSDataOutputStream out = fs.append(p, + EnumSet.of(CreateFlag.APPEND, CreateFlag.NEW_BLOCK), 4096, null)) { + out.write(toAppend); + } + } + /** * @return url content as string (UTF-8 encoding assumed) */ diff --git a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/CopyListing.java b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/CopyListing.java index 481aa61b0f1..9ebf9d29b3b 100644 --- a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/CopyListing.java +++ b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/CopyListing.java @@ -145,12 +145,22 @@ public abstract class CopyListing extends Configured { Configuration config = getConf(); FileSystem fs = pathToListFile.getFileSystem(config); - Path sortedList = DistCpUtils.sortListing(fs, config, pathToListFile); + final boolean splitLargeFile = options.splitLargeFile(); + + // When splitLargeFile is enabled, we don't randomize the copylist + // earlier, so we don't do the sorting here. For a file that has + // multiple entries due to split, we check here that their + // is continuous. + // + Path checkPath = splitLargeFile? + pathToListFile : DistCpUtils.sortListing(fs, config, pathToListFile); SequenceFile.Reader reader = new SequenceFile.Reader( - config, SequenceFile.Reader.file(sortedList)); + config, SequenceFile.Reader.file(checkPath)); try { Text lastKey = new Text("*"); //source relative path can never hold * + long lastChunkOffset = -1; + long lastChunkLength = -1; CopyListingFileStatus lastFileStatus = new CopyListingFileStatus(); Text currentKey = new Text(); @@ -161,8 +171,21 @@ public abstract class CopyListing extends Configured { if (currentKey.equals(lastKey)) { CopyListingFileStatus currentFileStatus = new CopyListingFileStatus(); reader.getCurrentValue(currentFileStatus); - throw new DuplicateFileException("File " + lastFileStatus.getPath() + " and " + - currentFileStatus.getPath() + " would cause duplicates. Aborting"); + if (!splitLargeFile) { + throw new DuplicateFileException("File " + lastFileStatus.getPath() + + " and " + currentFileStatus.getPath() + + " would cause duplicates. Aborting"); + } else { + if (lastChunkOffset + lastChunkLength != + currentFileStatus.getChunkOffset()) { + throw new InvalidInputException("File " + lastFileStatus.getPath() + + " " + lastChunkOffset + "," + lastChunkLength + + " and " + currentFileStatus.getPath() + + " " + currentFileStatus.getChunkOffset() + "," + + currentFileStatus.getChunkLength() + + " are not continuous. Aborting"); + } + } } reader.getCurrentValue(lastFileStatus); if (options.shouldPreserve(DistCpOptions.FileAttribute.ACL)) { @@ -181,8 +204,12 @@ public abstract class CopyListing extends Configured { xAttrSupportCheckFsSet.add(lastFsUri); } } - lastKey.set(currentKey); + lastKey.set(currentKey); + if (splitLargeFile) { + lastChunkOffset = lastFileStatus.getChunkOffset(); + lastChunkLength = lastFileStatus.getChunkLength(); + } if (options.shouldUseDiff() && LOG.isDebugEnabled()) { LOG.debug("Copy list entry " + idx + ": " + lastFileStatus.getPath().toUri().getPath()); diff --git a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/CopyListingFileStatus.java b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/CopyListingFileStatus.java index 00d4b325053..29c59ac1033 100644 --- a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/CopyListingFileStatus.java +++ b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/CopyListingFileStatus.java @@ -74,6 +74,14 @@ public final class CopyListingFileStatus implements Writable { private List aclEntries; private Map xAttrs; + // represents the offset and length of a file + // chunk in number of bytes. + // used when splitting a large file to chunks to copy in parallel. + // If a file is not large enough to split, chunkOffset would be 0 and + // chunkLength would be the length of the file. + private long chunkOffset = 0; + private long chunkLength = Long.MAX_VALUE; + /** * Default constructor. */ @@ -96,11 +104,32 @@ public final class CopyListingFileStatus implements Writable { fileStatus.getPath()); } + public CopyListingFileStatus(FileStatus fileStatus, + long chunkOffset, long chunkLength) { + this(fileStatus.getLen(), fileStatus.isDirectory(), + fileStatus.getReplication(), fileStatus.getBlockSize(), + fileStatus.getModificationTime(), fileStatus.getAccessTime(), + fileStatus.getPermission(), fileStatus.getOwner(), + fileStatus.getGroup(), + fileStatus.getPath()); + this.chunkOffset = chunkOffset; + this.chunkLength = chunkLength; + } + @SuppressWarnings("checkstyle:parameternumber") public CopyListingFileStatus(long length, boolean isdir, int blockReplication, long blocksize, long modificationTime, long accessTime, FsPermission permission, String owner, String group, Path path) { + this(length, isdir, blockReplication, blocksize, modificationTime, + accessTime, permission, owner, group, path, 0, Long.MAX_VALUE); + } + + @SuppressWarnings("checkstyle:parameternumber") + public CopyListingFileStatus(long length, boolean isdir, + int blockReplication, long blocksize, long modificationTime, + long accessTime, FsPermission permission, String owner, String group, + Path path, long chunkOffset, long chunkLength) { this.length = length; this.isdir = isdir; this.blockReplication = (short)blockReplication; @@ -117,6 +146,23 @@ public final class CopyListingFileStatus implements Writable { this.owner = (owner == null) ? "" : owner; this.group = (group == null) ? "" : group; this.path = path; + this.chunkOffset = chunkOffset; + this.chunkLength = chunkLength; + } + + public CopyListingFileStatus(CopyListingFileStatus other) { + this.length = other.length; + this.isdir = other.isdir; + this.blockReplication = other.blockReplication; + this.blocksize = other.blocksize; + this.modificationTime = other.modificationTime; + this.accessTime = other.accessTime; + this.permission = other.permission; + this.owner = other.owner; + this.group = other.group; + this.path = new Path(other.path.toUri()); + this.chunkOffset = other.chunkOffset; + this.chunkLength = other.chunkLength; } public Path getPath() { @@ -200,6 +246,31 @@ public final class CopyListingFileStatus implements Writable { this.xAttrs = xAttrs; } + public long getChunkOffset() { + return chunkOffset; + } + + public void setChunkOffset(long offset) { + this.chunkOffset = offset; + } + + public long getChunkLength() { + return chunkLength; + } + + public void setChunkLength(long chunkLength) { + this.chunkLength = chunkLength; + } + + public boolean isSplit() { + return getChunkLength() != Long.MAX_VALUE && + getChunkLength() != getLen(); + } + + public long getSizeToCopy() { + return isSplit()? getChunkLength() : getLen(); + } + @Override public void write(DataOutput out) throws IOException { Text.writeString(out, getPath().toString(), Text.DEFAULT_MAX_LEN); @@ -244,6 +315,9 @@ public final class CopyListingFileStatus implements Writable { } else { out.writeInt(NO_XATTRS); } + + out.writeLong(chunkOffset); + out.writeLong(chunkLength); } @Override @@ -292,6 +366,9 @@ public final class CopyListingFileStatus implements Writable { } else { xAttrs = null; } + + chunkOffset = in.readLong(); + chunkLength = in.readLong(); } @Override @@ -317,8 +394,14 @@ public final class CopyListingFileStatus implements Writable { public String toString() { StringBuilder sb = new StringBuilder(super.toString()); sb.append('{'); - sb.append("aclEntries = " + aclEntries); - sb.append(", xAttrs = " + xAttrs); + sb.append(this.getPath().toString()); + sb.append(" length = ").append(this.getLen()); + sb.append(" aclEntries = ").append(aclEntries); + sb.append(", xAttrs = ").append(xAttrs); + if (isSplit()) { + sb.append(", chunkOffset = ").append(this.getChunkOffset()); + sb.append(", chunkLength = ").append(this.getChunkLength()); + } sb.append('}'); return sb.toString(); } diff --git a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCp.java b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCp.java index ab58e9c66d8..8c2fa24a15c 100644 --- a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCp.java +++ b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCp.java @@ -35,6 +35,7 @@ import org.apache.hadoop.mapreduce.Cluster; import org.apache.hadoop.mapreduce.Job; import org.apache.hadoop.mapreduce.JobContext; import org.apache.hadoop.mapreduce.JobSubmissionFiles; +import org.apache.hadoop.tools.DistCpOptions.FileAttribute; import org.apache.hadoop.tools.CopyListing.*; import org.apache.hadoop.tools.mapred.CopyMapper; import org.apache.hadoop.tools.mapred.CopyOutputFormat; @@ -134,6 +135,7 @@ public class DistCp extends Configured implements Tool { try { inputOptions = (OptionsParser.parse(argv)); + setOptionsForSplitLargeFile(); setTargetPathExists(); LOG.info("Input Options: " + inputOptions); } catch (Throwable e) { @@ -235,6 +237,56 @@ public class DistCp extends Configured implements Tool { getConf().setBoolean(DistCpConstants.CONF_LABEL_TARGET_PATH_EXISTS, targetExists); } + + /** + * Check if concat is supported by fs. + * Throws UnsupportedOperationException if not. + */ + private void checkConcatSupport(FileSystem fs) { + try { + Path[] src = null; + Path tgt = null; + fs.concat(tgt, src); + } catch (UnsupportedOperationException use) { + throw new UnsupportedOperationException( + DistCpOptionSwitch.BLOCKS_PER_CHUNK.getSwitch() + + " is not supported since the target file system doesn't" + + " support concat.", use); + } catch (Exception e) { + // Ignore other exception + } + } + + /** + * Set up needed options for splitting large files. + */ + private void setOptionsForSplitLargeFile() throws IOException { + if (!inputOptions.splitLargeFile()) { + return; + } + Path target = inputOptions.getTargetPath(); + FileSystem targetFS = target.getFileSystem(getConf()); + checkConcatSupport(targetFS); + + LOG.info("Enabling preserving blocksize since " + + DistCpOptionSwitch.BLOCKS_PER_CHUNK.getSwitch() + " is passed."); + inputOptions.preserve(FileAttribute.BLOCKSIZE); + + LOG.info("Set " + + DistCpOptionSwitch.APPEND.getSwitch() + + " to false since " + DistCpOptionSwitch.BLOCKS_PER_CHUNK.getSwitch() + + " is passed."); + inputOptions.setAppend(false); + + LOG.info("Set " + + DistCpConstants.CONF_LABEL_SIMPLE_LISTING_RANDOMIZE_FILES + + " to false since " + DistCpOptionSwitch.BLOCKS_PER_CHUNK.getSwitch() + + " is passed."); + getConf().setBoolean( + DistCpConstants.CONF_LABEL_SIMPLE_LISTING_RANDOMIZE_FILES, false); + } + + /** * Create Job object for submitting it, with all the configuration * diff --git a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCpOptionSwitch.java b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCpOptionSwitch.java index fb47d769233..ced9b540c89 100644 --- a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCpOptionSwitch.java +++ b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCpOptionSwitch.java @@ -169,6 +169,16 @@ public enum DistCpOptionSwitch { new Option("sizelimit", true, "(Deprecated!) Limit number of files " + "copied to <= n bytes")), + BLOCKS_PER_CHUNK("", + new Option("blocksperchunk", true, "If set to a positive value, files" + + "with more blocks than this value will be split into chunks of " + + " blocks to be transferred in parallel, and " + + "reassembled on the destination. By default, is " + + "0 and the files will be transmitted in their entirety without " + + "splitting. This switch is only applicable when the source file " + + "system implements getBlockLocations method and the target file " + + "system implements concat method")), + /** * Specify bandwidth per map in MB, accepts bandwidth as a fraction */ diff --git a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCpOptions.java b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCpOptions.java index 8c37ff30ae0..9822d83dbd0 100644 --- a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCpOptions.java +++ b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCpOptions.java @@ -97,7 +97,11 @@ public class DistCpOptions { // targetPathExist is a derived field, it's initialized in the // beginning of distcp. private boolean targetPathExists = true; - + + // Size of chunk in number of blocks when splitting large file into chunks + // to copy in parallel. Default is 0 and file are not splitted. + private int blocksPerChunk = 0; + public static enum FileAttribute{ REPLICATION, BLOCKSIZE, USER, GROUP, PERMISSION, CHECKSUMTYPE, ACL, XATTR, TIMES; @@ -166,6 +170,7 @@ public class DistCpOptions { this.targetPath = that.getTargetPath(); this.targetPathExists = that.getTargetPathExists(); this.filtersFile = that.getFiltersFile(); + this.blocksPerChunk = that.blocksPerChunk; } } @@ -578,6 +583,18 @@ public class DistCpOptions { this.filtersFile = filtersFilename; } + public final void setBlocksPerChunk(int csize) { + this.blocksPerChunk = csize; + } + + public final int getBlocksPerChunk() { + return blocksPerChunk; + } + + public final boolean splitLargeFile() { + return blocksPerChunk > 0; + } + void validate() { if ((useDiff || useRdiff) && deleteMissing) { // -delete and -diff/-rdiff are mutually exclusive. For backward @@ -669,6 +686,8 @@ public class DistCpOptions { DistCpOptionSwitch.addToConf(conf, DistCpOptionSwitch.FILTERS, filtersFile); } + DistCpOptionSwitch.addToConf(conf, DistCpOptionSwitch.BLOCKS_PER_CHUNK, + String.valueOf(blocksPerChunk)); } /** @@ -704,6 +723,7 @@ public class DistCpOptions { ", targetPath=" + targetPath + ", targetPathExists=" + targetPathExists + ", filtersFile='" + filtersFile + '\'' + + ", blocksPerChunk=" + blocksPerChunk + '}'; } diff --git a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/OptionsParser.java b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/OptionsParser.java index d0f82ca76b5..8881264d281 100644 --- a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/OptionsParser.java +++ b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/OptionsParser.java @@ -172,11 +172,44 @@ public class OptionsParser { DistCpOptionSwitch.FILTERS.getSwitch())); } + parseBlocksPerChunk(command, option); + option.validate(); return option; } + + /** + * A helper method to parse chunk size in number of blocks. + * Used when breaking large file into chunks to copy in parallel. + * + * @param command command line arguments + */ + private static void parseBlocksPerChunk(CommandLine command, + DistCpOptions option) { + boolean hasOption = + command.hasOption(DistCpOptionSwitch.BLOCKS_PER_CHUNK.getSwitch()); + LOG.info("parseChunkSize: " + + DistCpOptionSwitch.BLOCKS_PER_CHUNK.getSwitch() + " " + hasOption); + if (hasOption) { + String chunkSizeString = getVal(command, + DistCpOptionSwitch.BLOCKS_PER_CHUNK.getSwitch().trim()); + try { + int csize = Integer.parseInt(chunkSizeString); + if (csize < 0) { + csize = 0; + } + LOG.info("Set distcp blocksPerChunk to " + csize); + option.setBlocksPerChunk(csize); + } + catch (NumberFormatException e) { + throw new IllegalArgumentException("blocksPerChunk is invalid: " + + chunkSizeString, e); + } + } + } + /** * parseSizeLimit is a helper method for parsing the deprecated * argument SIZE_LIMIT. @@ -211,8 +244,7 @@ public class OptionsParser { DistCpOptionSwitch.FILE_LIMIT.getSwitch().trim()); try { Integer.parseInt(fileLimitString); - } - catch (NumberFormatException e) { + } catch (NumberFormatException e) { throw new IllegalArgumentException("File-limit is invalid: " + fileLimitString, e); } diff --git a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/SimpleCopyListing.java b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/SimpleCopyListing.java index 105e4f2fe17..af913474550 100644 --- a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/SimpleCopyListing.java +++ b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/SimpleCopyListing.java @@ -19,6 +19,7 @@ package org.apache.hadoop.tools; import com.google.common.collect.Lists; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.fs.Path; @@ -47,6 +48,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Random; +import java.util.LinkedList; import static org.apache.hadoop.tools.DistCpConstants .HDFS_RESERVED_RAW_DIRECTORY_NAME; @@ -240,10 +242,10 @@ public class SimpleCopyListing extends CopyListing { final boolean preserveAcls = options.shouldPreserve(FileAttribute.ACL); final boolean preserveXAttrs = options.shouldPreserve(FileAttribute.XATTR); final boolean preserveRawXAttrs = options.shouldPreserveRawXattrs(); - CopyListingFileStatus fileCopyListingStatus = + LinkedList fileCopyListingStatus = DistCpUtils.toCopyListingFileStatus(sourceFS, fileStatus, - preserveAcls, preserveXAttrs, preserveRawXAttrs); - + preserveAcls, preserveXAttrs, preserveRawXAttrs, + options.getBlocksPerChunk()); writeToFileListingRoot(fileListWriter, fileCopyListingStatus, sourceRoot, options); } @@ -348,9 +350,10 @@ public class SimpleCopyListing extends CopyListing { FileStatus[] sourceFiles = sourceFS.listStatus(path); boolean explore = (sourceFiles != null && sourceFiles.length > 0); if (!explore || rootStatus.isDirectory()) { - CopyListingFileStatus rootCopyListingStatus = - DistCpUtils.toCopyListingFileStatus(sourceFS, rootStatus, - preserveAcls, preserveXAttrs, preserveRawXAttrs); + LinkedList rootCopyListingStatus = + DistCpUtils.toCopyListingFileStatus(sourceFS, rootStatus, + preserveAcls, preserveXAttrs, preserveRawXAttrs, + options.getBlocksPerChunk()); writeToFileListingRoot(fileListWriter, rootCopyListingStatus, sourcePathRoot, options); } @@ -360,20 +363,20 @@ public class SimpleCopyListing extends CopyListing { if (LOG.isDebugEnabled()) { LOG.debug("Recording source-path: " + sourceStatus.getPath() + " for copy."); } - CopyListingFileStatus sourceCopyListingStatus = - DistCpUtils.toCopyListingFileStatus(sourceFS, sourceStatus, - preserveAcls && sourceStatus.isDirectory(), - preserveXAttrs && sourceStatus.isDirectory(), - preserveRawXAttrs && sourceStatus.isDirectory()); - if (randomizeFileListing) { - addToFileListing(statusList, - new FileStatusInfo(sourceCopyListingStatus, sourcePathRoot), - fileListWriter); - } else { - writeToFileListing(fileListWriter, sourceCopyListingStatus, - sourcePathRoot); + LinkedList sourceCopyListingStatus = + DistCpUtils.toCopyListingFileStatus(sourceFS, sourceStatus, + preserveAcls && sourceStatus.isDirectory(), + preserveXAttrs && sourceStatus.isDirectory(), + preserveRawXAttrs && sourceStatus.isDirectory(), + options.getBlocksPerChunk()); + for (CopyListingFileStatus fs : sourceCopyListingStatus) { + if (randomizeFileListing) { + addToFileListing(statusList, + new FileStatusInfo(fs, sourcePathRoot), fileListWriter); + } else { + writeToFileListing(fileListWriter, fs, sourcePathRoot); + } } - if (sourceStatus.isDirectory()) { if (LOG.isDebugEnabled()) { LOG.debug("Adding source dir for traverse: " + sourceStatus.getPath()); @@ -641,18 +644,20 @@ public class SimpleCopyListing extends CopyListing { LOG.debug("Recording source-path: " + child.getPath() + " for copy."); } if (workResult.getSuccess()) { - CopyListingFileStatus childCopyListingStatus = + LinkedList childCopyListingStatus = DistCpUtils.toCopyListingFileStatus(sourceFS, child, preserveAcls && child.isDirectory(), preserveXAttrs && child.isDirectory(), - preserveRawXattrs && child.isDirectory()); - if (randomizeFileListing) { - addToFileListing(fileStatuses, - new FileStatusInfo(childCopyListingStatus, sourcePathRoot), - fileListWriter); - } else { - writeToFileListing(fileListWriter, childCopyListingStatus, - sourcePathRoot); + preserveRawXattrs && child.isDirectory(), + options.getBlocksPerChunk()); + + for (CopyListingFileStatus fs : childCopyListingStatus) { + if (randomizeFileListing) { + addToFileListing(fileStatuses, + new FileStatusInfo(fs, sourcePathRoot), fileListWriter); + } else { + writeToFileListing(fileListWriter, fs, sourcePathRoot); + } } } if (retry < maxRetries) { @@ -675,19 +680,21 @@ public class SimpleCopyListing extends CopyListing { } private void writeToFileListingRoot(SequenceFile.Writer fileListWriter, - CopyListingFileStatus fileStatus, Path sourcePathRoot, + LinkedList fileStatus, Path sourcePathRoot, DistCpOptions options) throws IOException { boolean syncOrOverwrite = options.shouldSyncFolder() || options.shouldOverwrite(); - if (fileStatus.getPath().equals(sourcePathRoot) && - fileStatus.isDirectory() && syncOrOverwrite) { - // Skip the root-paths when syncOrOverwrite - if (LOG.isDebugEnabled()) { - LOG.debug("Skip " + fileStatus.getPath()); - } - return; + for (CopyListingFileStatus fs : fileStatus) { + if (fs.getPath().equals(sourcePathRoot) && + fs.isDirectory() && syncOrOverwrite) { + // Skip the root-paths when syncOrOverwrite + if (LOG.isDebugEnabled()) { + LOG.debug("Skip " + fs.getPath()); + } + return; + } + writeToFileListing(fileListWriter, fs, sourcePathRoot); } - writeToFileListing(fileListWriter, fileStatus, sourcePathRoot); } private void writeToFileListing(SequenceFile.Writer fileListWriter, @@ -707,7 +714,7 @@ public class SimpleCopyListing extends CopyListing { fileListWriter.sync(); if (!fileStatus.isDirectory()) { - totalBytesToCopy += fileStatus.getLen(); + totalBytesToCopy += fileStatus.getSizeToCopy(); } else { totalDirs++; } diff --git a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/CopyCommitter.java b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/CopyCommitter.java index 75cefb488ae..6ddaab99c3d 100644 --- a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/CopyCommitter.java +++ b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/CopyCommitter.java @@ -27,6 +27,7 @@ import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.IOUtils; import org.apache.hadoop.io.SequenceFile; import org.apache.hadoop.io.Text; +import org.apache.hadoop.ipc.RemoteException; import org.apache.hadoop.mapreduce.JobContext; import org.apache.hadoop.mapreduce.JobStatus; import org.apache.hadoop.mapreduce.TaskAttemptContext; @@ -34,14 +35,17 @@ import org.apache.hadoop.mapreduce.lib.output.FileOutputCommitter; import org.apache.hadoop.tools.CopyListing; import org.apache.hadoop.tools.CopyListingFileStatus; import org.apache.hadoop.tools.DistCpConstants; +import org.apache.hadoop.tools.DistCpOptionSwitch; import org.apache.hadoop.tools.DistCpOptions; import org.apache.hadoop.tools.DistCpOptions.FileAttribute; import org.apache.hadoop.tools.GlobbedCopyListing; import org.apache.hadoop.tools.util.DistCpUtils; +import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; import java.util.EnumSet; +import java.util.LinkedList; import java.util.List; /** @@ -63,7 +67,8 @@ public class CopyCommitter extends FileOutputCommitter { private boolean syncFolder = false; private boolean overwrite = false; private boolean targetPathExists = true; - + private boolean ignoreFailures = false; + /** * Create a output committer * @@ -82,8 +87,13 @@ public class CopyCommitter extends FileOutputCommitter { Configuration conf = jobContext.getConfiguration(); syncFolder = conf.getBoolean(DistCpConstants.CONF_LABEL_SYNC_FOLDERS, false); overwrite = conf.getBoolean(DistCpConstants.CONF_LABEL_OVERWRITE, false); - targetPathExists = conf.getBoolean(DistCpConstants.CONF_LABEL_TARGET_PATH_EXISTS, true); - + targetPathExists = conf.getBoolean( + DistCpConstants.CONF_LABEL_TARGET_PATH_EXISTS, true); + ignoreFailures = conf.getBoolean( + DistCpOptionSwitch.IGNORE_FAILURES.getConfigLabel(), false); + + concatFileChunks(conf); + super.commitJob(jobContext); cleanupTempFiles(jobContext); @@ -169,9 +179,112 @@ public class CopyCommitter extends FileOutputCommitter { } } + private boolean isFileNotFoundException(IOException e) { + if (e instanceof FileNotFoundException) { + return true; + } + + if (e instanceof RemoteException) { + return ((RemoteException)e).unwrapRemoteException() + instanceof FileNotFoundException; + } + + return false; + } + + /** + * Concat chunk files for the same file into one. + * Iterate through copy listing, identify chunk files for the same file, + * concat them into one. + */ + private void concatFileChunks(Configuration conf) throws IOException { + + LOG.info("concat file chunks ..."); + + String spath = conf.get(DistCpConstants.CONF_LABEL_LISTING_FILE_PATH); + if (spath == null || spath.isEmpty()) { + return; + } + Path sourceListing = new Path(spath); + SequenceFile.Reader sourceReader = new SequenceFile.Reader(conf, + SequenceFile.Reader.file(sourceListing)); + Path targetRoot = + new Path(conf.get(DistCpConstants.CONF_LABEL_TARGET_WORK_PATH)); + + try { + CopyListingFileStatus srcFileStatus = new CopyListingFileStatus(); + Text srcRelPath = new Text(); + CopyListingFileStatus lastFileStatus = null; + LinkedList allChunkPaths = new LinkedList(); + + // Iterate over every source path that was copied. + while (sourceReader.next(srcRelPath, srcFileStatus)) { + if (srcFileStatus.isDirectory()) { + continue; + } + Path targetFile = new Path(targetRoot.toString() + "/" + srcRelPath); + Path targetFileChunkPath = + DistCpUtils.getSplitChunkPath(targetFile, srcFileStatus); + if (LOG.isDebugEnabled()) { + LOG.debug(" add " + targetFileChunkPath + " to concat."); + } + allChunkPaths.add(targetFileChunkPath); + if (srcFileStatus.getChunkOffset() + srcFileStatus.getChunkLength() + == srcFileStatus.getLen()) { + // This is the last chunk of the splits, consolidate allChunkPaths + try { + concatFileChunks(conf, targetFile, allChunkPaths); + } catch (IOException e) { + // If the concat failed because a chunk file doesn't exist, + // then we assume that the CopyMapper has skipped copying this + // file, and we ignore the exception here. + // If a chunk file should have been created but it was not, then + // the CopyMapper would have failed. + if (!isFileNotFoundException(e)) { + String emsg = "Failed to concat chunk files for " + targetFile; + if (!ignoreFailures) { + throw new IOException(emsg, e); + } else { + LOG.warn(emsg, e); + } + } + } + allChunkPaths.clear(); + lastFileStatus = null; + } else { + if (lastFileStatus == null) { + lastFileStatus = new CopyListingFileStatus(srcFileStatus); + } else { + // Two neighboring chunks have to be consecutive ones for the same + // file, for them to be merged + if (!srcFileStatus.getPath().equals(lastFileStatus.getPath()) || + srcFileStatus.getChunkOffset() != + (lastFileStatus.getChunkOffset() + + lastFileStatus.getChunkLength())) { + String emsg = "Inconsistent sequence file: current " + + "chunk file " + srcFileStatus + " doesnt match prior " + + "entry " + lastFileStatus; + if (!ignoreFailures) { + throw new IOException(emsg); + } else { + LOG.warn(emsg + ", skipping concat this set."); + } + } else { + lastFileStatus.setChunkOffset(srcFileStatus.getChunkOffset()); + lastFileStatus.setChunkLength(srcFileStatus.getChunkLength()); + } + } + } + } + } finally { + IOUtils.closeStream(sourceReader); + } + } + // This method changes the target-directories' file-attributes (owner, // user/group permissions, etc.) based on the corresponding source directories. - private void preserveFileAttributesForDirectories(Configuration conf) throws IOException { + private void preserveFileAttributesForDirectories(Configuration conf) + throws IOException { String attrSymbols = conf.get(DistCpConstants.CONF_LABEL_PRESERVE_STATUS); final boolean syncOrOverwrite = syncFolder || overwrite; @@ -325,4 +438,57 @@ public class CopyCommitter extends FileOutputCommitter { ", Unable to move to " + finalDir); } } + + /** + * Concat the passed chunk files into one and rename it the targetFile. + */ + private void concatFileChunks(Configuration conf, Path targetFile, + LinkedList allChunkPaths) throws IOException { + if (allChunkPaths.size() == 1) { + return; + } + if (LOG.isDebugEnabled()) { + LOG.debug("concat " + targetFile + " allChunkSize+ " + + allChunkPaths.size()); + } + FileSystem dstfs = targetFile.getFileSystem(conf); + + Path firstChunkFile = allChunkPaths.removeFirst(); + Path[] restChunkFiles = new Path[allChunkPaths.size()]; + allChunkPaths.toArray(restChunkFiles); + if (LOG.isDebugEnabled()) { + LOG.debug("concat: firstchunk: " + dstfs.getFileStatus(firstChunkFile)); + int i = 0; + for (Path f : restChunkFiles) { + LOG.debug("concat: other chunk: " + i + ": " + dstfs.getFileStatus(f)); + ++i; + } + } + dstfs.concat(firstChunkFile, restChunkFiles); + if (LOG.isDebugEnabled()) { + LOG.debug("concat: result: " + dstfs.getFileStatus(firstChunkFile)); + } + rename(dstfs, firstChunkFile, targetFile); + } + + /** + * Rename tmp to dst on destFileSys. + * @param destFileSys the file ssystem + * @param tmp the source path + * @param dst the destination path + * @throws IOException if renaming failed + */ + private static void rename(FileSystem destFileSys, Path tmp, Path dst) + throws IOException { + try { + if (destFileSys.exists(dst)) { + destFileSys.delete(dst, true); + } + destFileSys.rename(tmp, dst); + } catch (IOException ioe) { + throw new IOException("Fail to rename tmp file (=" + tmp + + ") to destination file (=" + dst + ")", ioe); + } + } + } diff --git a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/CopyMapper.java b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/CopyMapper.java index e1873f17e41..d6b3ba817b0 100644 --- a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/CopyMapper.java +++ b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/CopyMapper.java @@ -156,10 +156,12 @@ public class CopyMapper extends Mapper sourceFS = sourcePath.getFileSystem(conf); final boolean preserveXAttrs = fileAttributes.contains(FileAttribute.XATTR); - sourceCurrStatus = DistCpUtils.toCopyListingFileStatus(sourceFS, - sourceFS.getFileStatus(sourcePath), - fileAttributes.contains(FileAttribute.ACL), - preserveXAttrs, preserveRawXattrs); + sourceCurrStatus = DistCpUtils.toCopyListingFileStatusHelper(sourceFS, + sourceFS.getFileStatus(sourcePath), + fileAttributes.contains(FileAttribute.ACL), + preserveXAttrs, preserveRawXattrs, + sourceFileStatus.getChunkOffset(), + sourceFileStatus.getChunkLength()); } catch (FileNotFoundException e) { throw new IOException(new RetriableFileCopyCommand.CopyReadException(e)); } @@ -173,7 +175,8 @@ public class CopyMapper extends Mapper LOG.debug("Path could not be found: " + target, ignore); } - if (targetStatus != null && (targetStatus.isDirectory() != sourceCurrStatus.isDirectory())) { + if (targetStatus != null && + (targetStatus.isDirectory() != sourceCurrStatus.isDirectory())) { throw new IOException("Can't replace " + target + ". Target is " + getFileType(targetStatus) + ", Source is " + getFileType(sourceCurrStatus)); } @@ -183,19 +186,28 @@ public class CopyMapper extends Mapper return; } - FileAction action = checkUpdate(sourceFS, sourceCurrStatus, target, targetStatus); + FileAction action = checkUpdate(sourceFS, sourceCurrStatus, target, + targetStatus); + + Path tmpTarget = target; if (action == FileAction.SKIP) { LOG.info("Skipping copy of " + sourceCurrStatus.getPath() + " to " + target); updateSkipCounters(context, sourceCurrStatus); context.write(null, new Text("SKIP: " + sourceCurrStatus.getPath())); + } else { - copyFileWithRetry(description, sourceCurrStatus, target, context, + if (sourceCurrStatus.isSplit()) { + tmpTarget = DistCpUtils.getSplitChunkPath(target, sourceCurrStatus); + } + if (LOG.isDebugEnabled()) { + LOG.debug("copying " + sourceCurrStatus + " " + tmpTarget); + } + copyFileWithRetry(description, sourceCurrStatus, tmpTarget, context, action, fileAttributes); } - - DistCpUtils.preserve(target.getFileSystem(conf), target, sourceCurrStatus, - fileAttributes, preserveRawXattrs); + DistCpUtils.preserve(target.getFileSystem(conf), tmpTarget, + sourceCurrStatus, fileAttributes, preserveRawXattrs); } catch (IOException exception) { handleFailures(exception, sourceFileStatus, target, context); } @@ -261,8 +273,12 @@ public class CopyMapper extends Mapper private void handleFailures(IOException exception, CopyListingFileStatus sourceFileStatus, Path target, Context context) throws IOException, InterruptedException { - LOG.error("Failure in copying " + sourceFileStatus.getPath() + " to " + - target, exception); + LOG.error("Failure in copying " + sourceFileStatus.getPath() + + (sourceFileStatus.isSplit()? "," + + " offset=" + sourceFileStatus.getChunkOffset() + + " chunkLength=" + sourceFileStatus.getChunkLength() + : "") + + " to " + target, exception); if (ignoreFailures && ExceptionUtils.indexOfType(exception, CopyReadException.class) != -1) { diff --git a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/RetriableFileCopyCommand.java b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/RetriableFileCopyCommand.java index 06acd78a8a1..2c17fef1a38 100644 --- a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/RetriableFileCopyCommand.java +++ b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/RetriableFileCopyCommand.java @@ -118,17 +118,21 @@ public class RetriableFileCopyCommand extends RetriableCommand { .contains(FileAttribute.CHECKSUMTYPE) ? sourceFS .getFileChecksum(sourcePath) : null; - final long offset = action == FileAction.APPEND ? targetFS.getFileStatus( - target).getLen() : 0; + long offset = (action == FileAction.APPEND) ? + targetFS.getFileStatus(target).getLen() : source.getChunkOffset(); long bytesRead = copyToFile(targetPath, targetFS, source, offset, context, fileAttributes, sourceChecksum); - compareFileLengths(source, targetPath, configuration, bytesRead - + offset); + if (!source.isSplit()) { + compareFileLengths(source, targetPath, configuration, bytesRead + + offset); + } //At this point, src&dest lengths are same. if length==0, we skip checksum if ((bytesRead != 0) && (!skipCrc)) { - compareCheckSums(sourceFS, source.getPath(), sourceChecksum, - targetFS, targetPath); + if (!source.isSplit()) { + compareCheckSums(sourceFS, source.getPath(), sourceChecksum, + targetFS, targetPath); + } } // it's not append case, thus we first write to a temporary file, rename // it to the target path. @@ -249,16 +253,26 @@ public class RetriableFileCopyCommand extends RetriableCommand { ThrottledInputStream inStream = null; long totalBytesRead = 0; + long chunkLength = source2.getChunkLength(); + boolean finished = false; try { inStream = getInputStream(source, context.getConfiguration()); int bytesRead = readBytes(inStream, buf, sourceOffset); while (bytesRead >= 0) { + if (chunkLength > 0 && + (totalBytesRead + bytesRead) >= chunkLength) { + bytesRead = (int)(chunkLength - totalBytesRead); + finished = true; + } totalBytesRead += bytesRead; if (action == FileAction.APPEND) { sourceOffset += bytesRead; } outStream.write(buf, 0, bytesRead); updateContextStatus(totalBytesRead, context, source2); + if (finished) { + break; + } bytesRead = readBytes(inStream, buf, sourceOffset); } outStream.close(); diff --git a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/UniformSizeInputFormat.java b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/UniformSizeInputFormat.java index 3e86d0931bc..d1c18ea8d16 100644 --- a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/UniformSizeInputFormat.java +++ b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/UniformSizeInputFormat.java @@ -99,7 +99,8 @@ public class UniformSizeInputFormat while (reader.next(srcRelPath, srcFileStatus)) { // If adding the current file would cause the bytes per map to exceed // limit. Add the current file to new split - if (currentSplitSize + srcFileStatus.getLen() > nBytesPerSplit && lastPosition != 0) { + if (currentSplitSize + srcFileStatus.getChunkLength() > nBytesPerSplit + && lastPosition != 0) { FileSplit split = new FileSplit(listingFilePath, lastSplitStart, lastPosition - lastSplitStart, null); if (LOG.isDebugEnabled()) { @@ -109,7 +110,7 @@ public class UniformSizeInputFormat lastSplitStart = lastPosition; currentSplitSize = 0; } - currentSplitSize += srcFileStatus.getLen(); + currentSplitSize += srcFileStatus.getChunkLength(); lastPosition = reader.getPosition(); } if (lastPosition > lastSplitStart) { diff --git a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/util/DistCpUtils.java b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/util/DistCpUtils.java index 76bc4c56268..e315b848de4 100644 --- a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/util/DistCpUtils.java +++ b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/util/DistCpUtils.java @@ -19,9 +19,11 @@ package org.apache.hadoop.tools.util; import com.google.common.collect.Maps; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.BlockLocation; import org.apache.hadoop.fs.FileChecksum; import org.apache.hadoop.fs.FileStatus; import org.apache.hadoop.fs.FileSystem; @@ -30,6 +32,7 @@ import org.apache.hadoop.fs.XAttr; import org.apache.hadoop.fs.permission.AclEntry; import org.apache.hadoop.fs.permission.AclUtil; import org.apache.hadoop.fs.permission.FsPermission; +import org.apache.hadoop.hdfs.DistributedFileSystem; import org.apache.hadoop.io.SequenceFile; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.InputFormat; @@ -44,6 +47,7 @@ import org.apache.hadoop.util.StringUtils; import java.io.IOException; import java.text.DecimalFormat; import java.util.EnumSet; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -116,7 +120,7 @@ public class DistCpUtils { * @return Class implementing the strategy specified in options. */ public static Class getStrategy(Configuration conf, - DistCpOptions options) { + DistCpOptions options) { String confLabel = "distcp." + StringUtils.toLowerCase(options.getCopyStrategy()) + ".strategy" + ".impl"; @@ -297,6 +301,86 @@ public class DistCpUtils { return fileSystem.getXAttrs(path); } + /** + * Converts FileStatus to a list of CopyListingFileStatus. + * The resulted list contains either one CopyListingFileStatus per chunk of + * file-blocks (if file-size exceeds blockSize * blocksPerChunk, and there + * are more blocks in the file than blocksperChunk), or a single + * CopyListingFileStatus for the entire file (if file-size is too small to + * split). + * If preserving ACLs, populates the CopyListingFileStatus with the ACLs. + * If preserving XAttrs, populates the CopyListingFileStatus with the XAttrs. + * + * @param fileSystem FileSystem containing the file + * @param fileStatus FileStatus of file + * @param preserveAcls boolean true if preserving ACLs + * @param preserveXAttrs boolean true if preserving XAttrs + * @param preserveRawXAttrs boolean true if preserving raw.* XAttrs + * @param blocksPerChunk size of chunks when copying chunks in parallel + * @return list of CopyListingFileStatus + * @throws IOException if there is an I/O error + */ + public static LinkedList toCopyListingFileStatus( + FileSystem fileSystem, FileStatus fileStatus, boolean preserveAcls, + boolean preserveXAttrs, boolean preserveRawXAttrs, int blocksPerChunk) + throws IOException { + LinkedList copyListingFileStatus = + new LinkedList(); + + final CopyListingFileStatus clfs = toCopyListingFileStatusHelper( + fileSystem, fileStatus, preserveAcls, + preserveXAttrs, preserveRawXAttrs, + 0, fileStatus.getLen()); + final long blockSize = fileStatus.getBlockSize(); + if (LOG.isDebugEnabled()) { + LOG.debug("toCopyListing: " + fileStatus + " chunkSize: " + + blocksPerChunk + " isDFS: " + + (fileSystem instanceof DistributedFileSystem)); + } + if ((blocksPerChunk > 0) && + !fileStatus.isDirectory() && + (fileStatus.getLen() > blockSize * blocksPerChunk)) { + // split only when the file size is larger than the intended chunk size + final BlockLocation[] blockLocations; + blockLocations = fileSystem.getFileBlockLocations(fileStatus, 0, + fileStatus.getLen()); + + int numBlocks = blockLocations.length; + long curPos = 0; + if (numBlocks <= blocksPerChunk) { + if (LOG.isDebugEnabled()) { + LOG.debug(" add file " + clfs); + } + copyListingFileStatus.add(clfs); + } else { + int i = 0; + while (i < numBlocks) { + long curLength = 0; + for (int j = 0; j < blocksPerChunk && i < numBlocks; ++j, ++i) { + curLength += blockLocations[i].getLength(); + } + if (curLength > 0) { + CopyListingFileStatus clfs1 = new CopyListingFileStatus(clfs); + clfs1.setChunkOffset(curPos); + clfs1.setChunkLength(curLength); + if (LOG.isDebugEnabled()) { + LOG.debug(" add file chunk " + clfs1); + } + copyListingFileStatus.add(clfs1); + curPos += curLength; + } + } + } + } else { + if (LOG.isDebugEnabled()) { + LOG.debug(" add file/dir " + clfs); + } + copyListingFileStatus.add(clfs); + } + + return copyListingFileStatus; + } + /** * Converts a FileStatus to a CopyListingFileStatus. If preserving ACLs, * populates the CopyListingFileStatus with the ACLs. If preserving XAttrs, @@ -307,13 +391,17 @@ public class DistCpUtils { * @param preserveAcls boolean true if preserving ACLs * @param preserveXAttrs boolean true if preserving XAttrs * @param preserveRawXAttrs boolean true if preserving raw.* XAttrs + * @param chunkOffset chunk offset in bytes + * @param chunkLength chunk length in bytes + * @return CopyListingFileStatus * @throws IOException if there is an I/O error */ - public static CopyListingFileStatus toCopyListingFileStatus( + public static CopyListingFileStatus toCopyListingFileStatusHelper( FileSystem fileSystem, FileStatus fileStatus, boolean preserveAcls, - boolean preserveXAttrs, boolean preserveRawXAttrs) throws IOException { + boolean preserveXAttrs, boolean preserveRawXAttrs, + long chunkOffset, long chunkLength) throws IOException { CopyListingFileStatus copyListingFileStatus = - new CopyListingFileStatus(fileStatus); + new CopyListingFileStatus(fileStatus, chunkOffset, chunkLength); if (preserveAcls) { FsPermission perm = fileStatus.getPermission(); if (perm.getAclBit()) { @@ -470,4 +558,19 @@ public class DistCpUtils { return (sourceChecksum == null || targetChecksum == null || sourceChecksum.equals(targetChecksum)); } + + /* + * Return the Path for a given chunk. + * Used when splitting large file into chunks to copy in parallel. + * @param targetFile path to target file + * @param srcFileStatus source file status in copy listing + * @return path to the chunk specified by the parameters to store + * in target cluster temporarily + */ + public static Path getSplitChunkPath(Path targetFile, + CopyListingFileStatus srcFileStatus) { + return new Path(targetFile.toString() + + ".____distcpSplit____" + srcFileStatus.getChunkOffset() + + "." + srcFileStatus.getChunkLength()); + } } diff --git a/hadoop-tools/hadoop-distcp/src/site/markdown/DistCp.md.vm b/hadoop-tools/hadoop-distcp/src/site/markdown/DistCp.md.vm index 41a6e94aeee..a77deb2ffee 100644 --- a/hadoop-tools/hadoop-distcp/src/site/markdown/DistCp.md.vm +++ b/hadoop-tools/hadoop-distcp/src/site/markdown/DistCp.md.vm @@ -237,6 +237,7 @@ Flag | Description | Notes `-rdiff ` | Use snapshot diff report between given two snapshots to identify what has been changed on the target since the snapshot `` was created on the target, and apply the diff reversely to the target, and copy modified files from the source's ``, to make the target the same as ``. | This option is valid only with `-update` option and the following conditions should be satisfied.
    1. Both the source and the target FileSystem must be DistributedFileSystem. The source and the target can be two different clusters/paths, or they can be exactly the same cluster/path. In the latter case, modified files are copied from target's `` to target's current state).
    2. Two snapshots `` and `` have been created on the target FS, and `` is older than ``. No change has been made on target since `` was created on the target.
    3. The source has the same snapshot ``, which has the same content as the `` on the target. All the files/directories in the target's `` are the same with source's ``.
    | `-numListstatusThreads` | Number of threads to use for building file listing | At most 40 threads. `-skipcrccheck` | Whether to skip CRC checks between source and target paths. | +`-blocksperchunk ` | Number of blocks per chunk. When specified, split files into chunks to copy in parallel | If set to a positive value, files with more blocks than this value will be split into chunks of `` blocks to be transferred in parallel, and reassembled on the destination. By default, `` is 0 and the files will be transmitted in their entirety without splitting. This switch is only applicable when the source file system implements getBlockLocations method and the target file system implements concat method. | Architecture of DistCp ---------------------- diff --git a/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestDistCpSystem.java b/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestDistCpSystem.java index e3018a07730..b2266b3344d 100644 --- a/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestDistCpSystem.java +++ b/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestDistCpSystem.java @@ -23,17 +23,27 @@ import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.OutputStream; +import java.io.PrintStream; import java.net.URI; import java.util.ArrayList; import java.util.List; +import java.util.Random; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.FSDataInputStream; import org.apache.hadoop.fs.FileStatus; import org.apache.hadoop.fs.FileSystem; +import org.apache.hadoop.fs.FsShell; import org.apache.hadoop.fs.Path; +import org.apache.hadoop.hdfs.DFSConfigKeys; +import org.apache.hadoop.hdfs.DFSTestUtil; +import org.apache.hadoop.hdfs.DistributedFileSystem; import org.apache.hadoop.hdfs.MiniDFSCluster; +import org.apache.hadoop.hdfs.client.HdfsClientConfigKeys; import org.apache.hadoop.util.ToolRunner; import org.junit.AfterClass; import org.junit.Assert; @@ -47,11 +57,15 @@ import org.junit.rules.Timeout; */ public class TestDistCpSystem { + private static final Log LOG = + LogFactory.getLog(TestDistCpSystem.class); + @Rule public Timeout globalTimeout = new Timeout(30000); private static final String SRCDAT = "srcdat"; private static final String DSTDAT = "dstdat"; + private static final long BLOCK_SIZE = 1024; private static MiniDFSCluster cluster; private static Configuration conf; @@ -63,27 +77,76 @@ public class TestDistCpSystem { this.path = path; this.isDir = isDir; } - String getPath() { return path; } - boolean isDirectory() { return isDir; } + + String getPath() { + return path; + } + + boolean isDirectory() { + return isDir; + } + } + + @BeforeClass + public static void beforeClass() throws IOException { + conf = new Configuration(); + conf.setLong(DFSConfigKeys.DFS_NAMENODE_MIN_BLOCK_SIZE_KEY, BLOCK_SIZE); + conf.setLong(DFSConfigKeys.DFS_BLOCK_SIZE_KEY, BLOCK_SIZE); + cluster = new MiniDFSCluster.Builder(conf).numDataNodes(2).build(); + cluster.waitActive(); + } + + @AfterClass + public static void afterClass() throws IOException { + if (cluster != null) { + cluster.shutdown(); + } + } + + static String execCmd(FsShell shell, String... args) throws Exception { + ByteArrayOutputStream baout = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(baout, true); + PrintStream old = System.out; + System.setOut(out); + shell.run(args); + out.close(); + System.setOut(old); + return baout.toString(); } - private void createFiles(FileSystem fs, String topdir, - FileEntry[] entries) throws IOException { + private void createFiles(DistributedFileSystem fs, String topdir, + FileEntry[] entries, long chunkSize) throws IOException { + long seed = System.currentTimeMillis(); + Random rand = new Random(seed); + short replicationFactor = 2; for (FileEntry entry : entries) { - Path newpath = new Path(topdir + "/" + entry.getPath()); + Path newPath = new Path(topdir + "/" + entry.getPath()); if (entry.isDirectory()) { - fs.mkdirs(newpath); + fs.mkdirs(newPath); } else { - OutputStream out = fs.create(newpath); - try { - out.write((topdir + "/" + entry).getBytes()); - out.write("\n".getBytes()); - } finally { - out.close(); + long fileSize = BLOCK_SIZE *100; + int bufSize = 128; + if (chunkSize == -1) { + DFSTestUtil.createFile(fs, newPath, bufSize, + fileSize, BLOCK_SIZE, replicationFactor, seed); + } else { + // Create a variable length block file, by creating + // one block of half block size at the chunk boundary + long seg1 = chunkSize * BLOCK_SIZE - BLOCK_SIZE / 2; + long seg2 = fileSize - seg1; + DFSTestUtil.createFile(fs, newPath, bufSize, + seg1, BLOCK_SIZE, replicationFactor, seed); + DFSTestUtil.appendFileNewBlock(fs, newPath, (int)seg2); } } + seed = System.currentTimeMillis() + rand.nextLong(); } } + + private void createFiles(DistributedFileSystem fs, String topdir, + FileEntry[] entries) throws IOException { + createFiles(fs, topdir, entries, -1); + } private static FileStatus[] getFileStatus(FileSystem fs, String topdir, FileEntry[] files) throws IOException { @@ -104,18 +167,19 @@ public class TestDistCpSystem { } private void testPreserveUserHelper(String testRoot, - FileEntry[] srcEntries, - FileEntry[] dstEntries, - boolean createSrcDir, - boolean createTgtDir, - boolean update) throws Exception { + FileEntry[] srcEntries, + FileEntry[] dstEntries, + boolean createSrcDir, + boolean createTgtDir, + boolean update) throws Exception { final String testSrcRel = SRCDAT; final String testSrc = testRoot + "/" + testSrcRel; final String testDstRel = DSTDAT; final String testDst = testRoot + "/" + testDstRel; String nnUri = FileSystem.getDefaultUri(conf).toString(); - FileSystem fs = FileSystem.get(URI.create(nnUri), conf); + DistributedFileSystem fs = (DistributedFileSystem) + FileSystem.get(URI.create(nnUri), conf); fs.mkdirs(new Path(testRoot)); if (createSrcDir) { fs.mkdirs(new Path(testSrc)); @@ -129,8 +193,8 @@ public class TestDistCpSystem { for(int i = 0; i < srcEntries.length; i++) { fs.setOwner(srcstats[i].getPath(), "u" + i, null); } - String[] args = update? new String[]{"-pu", "-update", nnUri+testSrc, - nnUri+testDst} : new String[]{"-pu", nnUri+testSrc, nnUri+testDst}; + String[] args = update? new String[]{"-pub", "-update", nnUri+testSrc, + nnUri+testDst} : new String[]{"-pub", nnUri+testSrc, nnUri+testDst}; ToolRunner.run(conf, new DistCp(), args); @@ -145,20 +209,263 @@ public class TestDistCpSystem { deldir(fs, testRoot); } - @BeforeClass - public static void beforeClass() throws IOException { - conf = new Configuration(); - cluster = new MiniDFSCluster.Builder(conf).numDataNodes(2).build(); - cluster.waitActive(); + private void compareFiles(FileSystem fs, FileStatus srcStat, + FileStatus dstStat) throws Exception { + LOG.info("Comparing " + srcStat + " and " + dstStat); + assertEquals(srcStat.isDirectory(), dstStat.isDirectory()); + assertEquals(srcStat.getReplication(), dstStat.getReplication()); + assertEquals("File POSIX permission should match", + srcStat.getPermission(), dstStat.getPermission()); + assertEquals("File user ownership should match", + srcStat.getOwner(), dstStat.getOwner()); + assertEquals("File group ownership should match", + srcStat.getGroup(), dstStat.getGroup()); + // TODO; check ACL attributes + + if (srcStat.isDirectory()) { + return; + } + + assertEquals("File length should match (" + srcStat.getPath() + ")", + srcStat.getLen(), dstStat.getLen()); + + FSDataInputStream srcIn = fs.open(srcStat.getPath()); + FSDataInputStream dstIn = fs.open(dstStat.getPath()); + try { + byte[] readSrc = new byte[(int) + HdfsClientConfigKeys.DFS_BLOCK_SIZE_DEFAULT]; + byte[] readDst = new byte[(int) + HdfsClientConfigKeys.DFS_BLOCK_SIZE_DEFAULT]; + + int srcBytesRead = 0, tgtBytesRead = 0; + int srcIdx = 0, tgtIdx = 0; + long totalComparedBytes = 0; + while (true) { + if (srcBytesRead == 0) { + srcBytesRead = srcIn.read(readSrc); + srcIdx = 0; + } + if (tgtBytesRead == 0) { + tgtBytesRead = dstIn.read(readDst); + tgtIdx = 0; + } + if (srcBytesRead == 0 || tgtBytesRead == 0) { + LOG.info("______ compared src and dst files for " + + totalComparedBytes + " bytes, content match."); + if (srcBytesRead != tgtBytesRead) { + Assert.fail("Read mismatching size, compared " + + totalComparedBytes + " bytes between src and dst file " + + srcStat + " and " + dstStat); + } + if (totalComparedBytes != srcStat.getLen()) { + Assert.fail("Only read/compared " + totalComparedBytes + + " bytes between src and dst file " + srcStat + + " and " + dstStat); + } else { + // success + break; + } + } + for (; srcIdx < srcBytesRead && tgtIdx < tgtBytesRead; + ++srcIdx, ++tgtIdx) { + if (readSrc[srcIdx] != readDst[tgtIdx]) { + Assert.fail("src and dst file does not match at " + + totalComparedBytes + " between " + + srcStat + " and " + dstStat); + } + ++totalComparedBytes; + } + LOG.info("______ compared src and dst files for " + + totalComparedBytes + " bytes, content match. FileLength: " + + srcStat.getLen()); + if (totalComparedBytes == srcStat.getLen()) { + LOG.info("______ Final:" + srcIdx + " " + + srcBytesRead + " " + tgtIdx + " " + tgtBytesRead); + break; + } + if (srcIdx == srcBytesRead) { + srcBytesRead = 0; + } + if (tgtIdx == tgtBytesRead) { + tgtBytesRead = 0; + } + } + } finally { + if (srcIn != null) { + srcIn.close(); + } + if (dstIn != null) { + dstIn.close(); + } + } } - @AfterClass - public static void afterClass() throws IOException { - if (cluster != null) { - cluster.shutdown(); + // WC: needed because the current distcp does not create target dirs + private void createDestDir(FileSystem fs, String testDst, + FileStatus[] srcStats, FileEntry[] srcFiles) throws IOException { + fs.mkdirs(new Path(testDst)); + + for (int i=0; i=0; --i) { + if (!srcFiles[i].isDirectory()) { + LOG.info("Modifying " + srcStats[i].getPath()); + DFSTestUtil.appendFileNewBlock(fs, srcStats[i].getPath(), + (int)BLOCK_SIZE * 3); + break; + } + } + // get file status after modifying file + srcStats = getFileStatus(fs, testRoot, srcFiles); + + args = new String[] {"-pugp", "-update", "-blocksperchunk", + String.valueOf(chunkSize), + nnUri + testSrc, nnUri + testDst + "/" + testSrcRel}; + + copyAndVerify(fs, srcFiles, srcStats, testDst, args); + + deldir(fs, testRoot); + } + + @Test + public void testRecursiveChunkCopy() throws Exception { + FileEntry[] srcFiles = { + new FileEntry(SRCDAT, true), + new FileEntry(SRCDAT + "/file0", false), + new FileEntry(SRCDAT + "/dir1", true), + new FileEntry(SRCDAT + "/dir2", true), + new FileEntry(SRCDAT + "/dir1/file1", false) + }; + chunkCopy(srcFiles); + } + + @Test + public void testChunkCopyOneFile() throws Exception { + FileEntry[] srcFiles = { + new FileEntry(SRCDAT, true), + new FileEntry(SRCDAT + "/file0", false) + }; + chunkCopy(srcFiles); + } + + @Test + public void testDistcpLargeFile() throws Exception { + FileEntry[] srcfiles = { + new FileEntry(SRCDAT, true), + new FileEntry(SRCDAT + "/file", false) + }; + + final String testRoot = "/testdir"; + final String testSrcRel = SRCDAT; + final String testSrc = testRoot + "/" + testSrcRel; + final String testDstRel = DSTDAT; + final String testDst = testRoot + "/" + testDstRel; + + String nnUri = FileSystem.getDefaultUri(conf).toString(); + DistributedFileSystem fs = + (DistributedFileSystem) FileSystem.get(URI.create(nnUri), conf); + fs.mkdirs(new Path(testRoot)); + fs.mkdirs(new Path(testSrc)); + fs.mkdirs(new Path(testDst)); + long chunkSize = 6; + createFiles(fs, testRoot, srcfiles, chunkSize); + + String srcFileName = testRoot + Path.SEPARATOR + srcfiles[1].getPath(); + Path srcfile = new Path(srcFileName); + + if(!cluster.getFileSystem().exists(srcfile)){ + throw new Exception("src not exist"); + } + + final long srcLen = fs.getFileStatus(srcfile).getLen(); + + FileStatus[] srcstats = getFileStatus(fs, testRoot, srcfiles); + for (int i = 0; i < srcfiles.length; i++) { + fs.setOwner(srcstats[i].getPath(), "u" + i, null); + } + String[] args = new String[] { + "-blocksperchunk", + String.valueOf(chunkSize), + nnUri + testSrc, + nnUri + testDst + }; + + LOG.info("_____ running distcp: " + args[0] + " " + args[1]); + ToolRunner.run(conf, new DistCp(), args); + + String realTgtPath = testDst; + FileStatus[] dststat = getFileStatus(fs, realTgtPath, srcfiles); + assertEquals("File length should match", srcLen, + dststat[dststat.length - 1].getLen()); + + this.compareFiles(fs, srcstats[srcstats.length-1], + dststat[dststat.length-1]); + deldir(fs, testRoot); + } + @Test public void testPreserveUseNonEmptyDir() throws Exception { String testRoot = "/testdir." + getMethodName(); @@ -180,7 +487,6 @@ public class TestDistCpSystem { testPreserveUserHelper(testRoot, srcfiles, dstfiles, false, false, false); } - @Test public void testPreserveUserEmptyDir() throws Exception { String testRoot = "/testdir." + getMethodName(); diff --git a/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestOptionsParser.java b/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestOptionsParser.java index efe46272e38..f94ba97ec34 100644 --- a/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestOptionsParser.java +++ b/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestOptionsParser.java @@ -394,7 +394,7 @@ public class TestOptionsParser { + "copyStrategy='uniformsize', preserveStatus=[], " + "preserveRawXattrs=false, atomicWorkPath=null, logPath=null, " + "sourceFileListing=abc, sourcePaths=null, targetPath=xyz, " - + "targetPathExists=true, filtersFile='null'}"; + + "targetPathExists=true, filtersFile='null', blocksPerChunk=0}"; String optionString = option.toString(); Assert.assertEquals(val, optionString); Assert.assertNotSame(DistCpOptionSwitch.ATOMIC_COMMIT.toString(), diff --git a/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/mapred/TestCopyCommitter.java b/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/mapred/TestCopyCommitter.java index 2e9a350b1ca..2452d6fee3d 100644 --- a/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/mapred/TestCopyCommitter.java +++ b/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/mapred/TestCopyCommitter.java @@ -81,6 +81,10 @@ public class TestCopyCommitter { @Before public void createMetaFolder() { config.set(DistCpConstants.CONF_LABEL_META_FOLDER, "/meta"); + // Unset listing file path since the config is shared by + // multiple tests, and some test doesn't set it, such as + // testNoCommitAction, but the distcp code will check it. + config.set(DistCpConstants.CONF_LABEL_LISTING_FILE_PATH, ""); Path meta = new Path("/meta"); try { cluster.getFileSystem().mkdirs(meta); @@ -326,7 +330,6 @@ public class TestCopyCommitter { committer.commitJob(jobContext); Assert.assertFalse(fs.exists(new Path(workPath))); Assert.assertTrue(fs.exists(new Path(finalPath))); - } catch (IOException e) { LOG.error("Exception encountered while testing for preserve status", e); Assert.fail("Atomic commit failure"); From 28cdc5a8dc37ade1f45bda3aede589ee8593945e Mon Sep 17 00:00:00 2001 From: Hanisha Koneru Date: Thu, 30 Mar 2017 22:41:26 -0700 Subject: [PATCH 174/188] HDFS-11551. Handle SlowDiskReport from DataNode at the NameNode. Contributed by Hanisha Koneru. --- .../hdfs/server/protocol/SlowDiskReports.java | 28 +- .../blockmanagement/DatanodeManager.java | 34 +- .../blockmanagement/SlowDiskTracker.java | 291 ++++++++++++ .../datanode/metrics/DataNodeDiskMetrics.java | 35 +- .../blockmanagement/TestSlowDiskTracker.java | 448 ++++++++++++++++++ 5 files changed, 812 insertions(+), 24 deletions(-) create mode 100644 hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/blockmanagement/SlowDiskTracker.java create mode 100644 hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/blockmanagement/TestSlowDiskTracker.java diff --git a/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/server/protocol/SlowDiskReports.java b/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/server/protocol/SlowDiskReports.java index ef4d09e884d..8095c2a690a 100644 --- a/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/server/protocol/SlowDiskReports.java +++ b/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/server/protocol/SlowDiskReports.java @@ -48,7 +48,7 @@ public final class SlowDiskReports { private final Map> slowDisks; /** - * An object representing a SlowPeerReports with no entries. Should + * An object representing a SlowDiskReports with no entries. Should * be used instead of null or creating new objects when there are * no slow peers to report. */ @@ -119,8 +119,28 @@ public final class SlowDiskReports { * Lists the types of operations on which disk latencies are measured. */ public enum DiskOp { - METADATA, - READ, - WRITE + METADATA("MetadataOp"), + READ("ReadIO"), + WRITE("WriteIO"); + + private final String value; + + DiskOp(final String v) { + this.value = v; + } + + @Override + public String toString() { + return value; + } + + public static DiskOp fromValue(final String value) { + for (DiskOp as : DiskOp.values()) { + if (as.value.equals(value)) { + return as; + } + } + return null; + } } } diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/blockmanagement/DatanodeManager.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/blockmanagement/DatanodeManager.java index e22b7afa2a3..18135a8a549 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/blockmanagement/DatanodeManager.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/blockmanagement/DatanodeManager.java @@ -38,6 +38,7 @@ import org.apache.hadoop.hdfs.protocol.HdfsConstants.DatanodeReportType; import org.apache.hadoop.hdfs.security.token.block.BlockTokenIdentifier; import org.apache.hadoop.hdfs.server.blockmanagement.DatanodeDescriptor.BlockTargetPair; import org.apache.hadoop.hdfs.server.blockmanagement.DatanodeDescriptor.CachedBlocksList; +import org.apache.hadoop.hdfs.server.common.Util; import org.apache.hadoop.hdfs.server.namenode.CachedBlock; import org.apache.hadoop.hdfs.server.namenode.NameNode; import org.apache.hadoop.hdfs.server.namenode.Namesystem; @@ -180,9 +181,15 @@ public class DatanodeManager { * True if we should process latency metrics from downstream peers. */ private final boolean dataNodePeerStatsEnabled; + /** + * True if we should process latency metrics from individual DN disks. + */ + private final boolean dataNodeDiskStatsEnabled; @Nullable private final SlowPeerTracker slowPeerTracker; + @Nullable + private final SlowDiskTracker slowDiskTracker; /** * The minimum time between resending caching directives to Datanodes, @@ -208,9 +215,16 @@ public class DatanodeManager { this.dataNodePeerStatsEnabled = conf.getBoolean( DFSConfigKeys.DFS_DATANODE_PEER_STATS_ENABLED_KEY, DFSConfigKeys.DFS_DATANODE_PEER_STATS_ENABLED_DEFAULT); + this.dataNodeDiskStatsEnabled = Util.isDiskStatsEnabled(conf.getDouble( + DFSConfigKeys.DFS_DATANODE_FILEIO_PROFILING_SAMPLING_FRACTION_KEY, + DFSConfigKeys.DFS_DATANODE_FILEIO_PROFILING_SAMPLING_FRACTION_DEFAULT)); + final Timer timer = new Timer(); this.slowPeerTracker = dataNodePeerStatsEnabled ? - new SlowPeerTracker(conf, new Timer()) : null; + new SlowPeerTracker(conf, timer) : null; + + this.slowDiskTracker = dataNodeDiskStatsEnabled ? + new SlowDiskTracker(conf, timer) : null; this.defaultXferPort = NetUtils.createSocketAddr( conf.getTrimmed(DFSConfigKeys.DFS_DATANODE_ADDRESS_KEY, @@ -1664,6 +1678,16 @@ public class DatanodeManager { } } + if (slowDiskTracker != null) { + if (!slowDisks.getSlowDisks().isEmpty()) { + if (LOG.isDebugEnabled()) { + LOG.debug("DataNode " + nodeReg + " reported slow disks: " + + slowDisks.getSlowDisks()); + } + slowDiskTracker.addSlowDiskReport(nodeReg.getIpcAddr(false), slowDisks); + } + } + if (!cmds.isEmpty()) { return cmds.toArray(new DatanodeCommand[cmds.size()]); } @@ -1875,5 +1899,13 @@ public class DatanodeManager { public String getSlowPeersReport() { return slowPeerTracker != null ? slowPeerTracker.getJson() : null; } + + /** + * Use only for testing. + */ + @VisibleForTesting + public SlowDiskTracker getSlowDiskTracker() { + return slowDiskTracker; + } } diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/blockmanagement/SlowDiskTracker.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/blockmanagement/SlowDiskTracker.java new file mode 100644 index 00000000000..25920a2e07f --- /dev/null +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/blockmanagement/SlowDiskTracker.java @@ -0,0 +1,291 @@ +/** + * 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.blockmanagement; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.google.common.primitives.Doubles; +import org.apache.hadoop.classification.InterfaceAudience; +import org.apache.hadoop.classification.InterfaceStability; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hdfs.DFSConfigKeys; +import org.apache.hadoop.hdfs.server.protocol.SlowDiskReports; +import org.apache.hadoop.hdfs.server.protocol.SlowDiskReports.DiskOp; +import org.apache.hadoop.util.Timer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Map; +import java.util.PriorityQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * This class aggregates information from {@link SlowDiskReports} received via + * heartbeats. + */ +@InterfaceAudience.Private +@InterfaceStability.Unstable +public class SlowDiskTracker { + public static final Logger LOG = + LoggerFactory.getLogger(SlowPeerTracker.class); + + /** + * Time duration after which a report is considered stale. This is + * set to DFS_DATANODE_OUTLIERS_REPORT_INTERVAL_KEY * 3 i.e. + * maintained for at least two successive reports. + */ + private long reportValidityMs; + + /** + * Timer object for querying the current time. Separated out for + * unit testing. + */ + private final Timer timer; + + /** + * Number of disks to include in JSON report per operation. We will return + * disks with the highest latency. + */ + private static final int MAX_DISKS_TO_REPORT = 5; + private static final String DATANODE_DISK_SEPARATOR = ":"; + private final long reportGenerationIntervalMs; + + private volatile long lastUpdateTime; + private AtomicBoolean isUpdateInProgress = new AtomicBoolean(false); + + /** + * Information about disks that have been reported as being slow. + * It is map of (Slow Disk ID) -> (DiskLatency). The DiskLatency contains + * the disk ID, the latencies reported and the timestamp when the report + * was received. + */ + private final Map diskIDLatencyMap; + + /** + * Map of slow disk -> diskOperations it has been reported slow in. + */ + private volatile ArrayList slowDisksReport = + Lists.newArrayList(); + private volatile ArrayList oldSlowDisksCheck; + + public SlowDiskTracker(Configuration conf, Timer timer) { + this.timer = timer; + this.lastUpdateTime = timer.monotonicNow(); + this.diskIDLatencyMap = new ConcurrentHashMap<>(); + this.reportGenerationIntervalMs = conf.getTimeDuration( + DFSConfigKeys.DFS_DATANODE_OUTLIERS_REPORT_INTERVAL_KEY, + DFSConfigKeys.DFS_DATANODE_OUTLIERS_REPORT_INTERVAL_DEFAULT, + TimeUnit.MILLISECONDS); + this.reportValidityMs = reportGenerationIntervalMs * 3; + } + + @VisibleForTesting + public static String getSlowDiskIDForReport(String datanodeID, + String slowDisk) { + return datanodeID + DATANODE_DISK_SEPARATOR + slowDisk; + } + + public void addSlowDiskReport(String dataNodeID, + SlowDiskReports dnSlowDiskReport) { + Map> slowDisks = + dnSlowDiskReport.getSlowDisks(); + + long now = timer.monotonicNow(); + + for (Map.Entry> slowDiskEntry : + slowDisks.entrySet()) { + + String diskID = getSlowDiskIDForReport(dataNodeID, + slowDiskEntry.getKey()); + + Map latencies = slowDiskEntry.getValue(); + + DiskLatency diskLatency = new DiskLatency(diskID, latencies, now); + diskIDLatencyMap.put(diskID, diskLatency); + } + + checkAndUpdateReportIfNecessary(); + } + + private void checkAndUpdateReportIfNecessary() { + // Check if it is time for update + long now = timer.monotonicNow(); + if (now - lastUpdateTime > reportGenerationIntervalMs) { + updateSlowDiskReportAsync(now); + } + } + + @VisibleForTesting + public void updateSlowDiskReportAsync(long now) { + if (isUpdateInProgress.compareAndSet(false, true)) { + lastUpdateTime = now; + new Thread(new Runnable() { + @Override + public void run() { + slowDisksReport = getSlowDisks(diskIDLatencyMap, + MAX_DISKS_TO_REPORT, now); + + cleanUpOldReports(now); + + isUpdateInProgress.set(false); + } + }).start(); + } + } + + /** + * This structure is a thin wrapper over disk latencies. + */ + public static class DiskLatency { + @JsonProperty("SlowDiskID") + final private String slowDiskID; + @JsonProperty("Latencies") + final private Map latencyMap; + @JsonIgnore + private long timestamp; + + /** + * Constructor needed by Jackson for Object mapping. + */ + public DiskLatency( + @JsonProperty("SlowDiskID") String slowDiskID, + @JsonProperty("Latencies") Map latencyMap) { + this.slowDiskID = slowDiskID; + this.latencyMap = latencyMap; + } + + public DiskLatency(String slowDiskID, Map latencyMap, + long timestamp) { + this.slowDiskID = slowDiskID; + this.latencyMap = latencyMap; + this.timestamp = timestamp; + } + + String getSlowDiskID() { + return this.slowDiskID; + } + + double getMaxLatency() { + double maxLatency = 0; + for (double latency : latencyMap.values()) { + if (latency > maxLatency) { + maxLatency = latency; + } + } + return maxLatency; + } + + Double getLatency(DiskOp op) { + return this.latencyMap.get(op); + } + } + + /** + * Retrieve a list of stop low disks i.e disks with the highest max latencies. + * @param numDisks number of disks to return. This is to limit the size of + * the generated JSON. + */ + private ArrayList getSlowDisks( + Map reports, int numDisks, long now) { + if (reports.isEmpty()) { + return new ArrayList(ImmutableList.of()); + } + + final PriorityQueue topNReports = new PriorityQueue<>( + reports.size(), + new Comparator() { + @Override + public int compare(DiskLatency o1, DiskLatency o2) { + return Doubles.compare( + o1.getMaxLatency(), o2.getMaxLatency()); + } + }); + + ArrayList oldSlowDiskIDs = Lists.newArrayList(); + + for (Map.Entry entry : reports.entrySet()) { + DiskLatency diskLatency = entry.getValue(); + if (now - diskLatency.timestamp < reportValidityMs) { + if (topNReports.size() < numDisks) { + topNReports.add(diskLatency); + } else if (topNReports.peek().getMaxLatency() < + diskLatency.getMaxLatency()) { + topNReports.poll(); + topNReports.add(diskLatency); + } + } else { + oldSlowDiskIDs.add(diskLatency); + } + } + + oldSlowDisksCheck = oldSlowDiskIDs; + + return Lists.newArrayList(topNReports); + } + + /** + * Retrieve all valid reports as a JSON string. + * @return serialized representation of valid reports. null if + * serialization failed. + */ + public String getSlowDiskReportAsJsonString() { + ObjectMapper objectMapper = new ObjectMapper(); + try { + return objectMapper.writeValueAsString(slowDisksReport); + } catch (JsonProcessingException e) { + // Failed to serialize. Don't log the exception call stack. + LOG.debug("Failed to serialize statistics" + e); + return null; + } + } + + private void cleanUpOldReports(long now) { + if (oldSlowDisksCheck != null) { + for (DiskLatency oldDiskLatency : oldSlowDisksCheck) { + diskIDLatencyMap.remove(oldDiskLatency.getSlowDiskID(), oldDiskLatency); + } + } + // Replace oldSlowDiskIDsCheck with an empty ArrayList + oldSlowDisksCheck = null; + } + + @VisibleForTesting + ArrayList getSlowDisksReport() { + return this.slowDisksReport; + } + + @VisibleForTesting + long getReportValidityMs() { + return reportValidityMs; + } + + @VisibleForTesting + void setReportValidityMs(long reportValidityMs) { + this.reportValidityMs = reportValidityMs; + } +} diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/metrics/DataNodeDiskMetrics.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/metrics/DataNodeDiskMetrics.java index 2602b012a64..f2954e824dc 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/metrics/DataNodeDiskMetrics.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/metrics/DataNodeDiskMetrics.java @@ -20,7 +20,6 @@ package org.apache.hadoop.hdfs.server.datanode.metrics; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; -import com.google.common.collect.Sets; import org.apache.hadoop.classification.InterfaceAudience; import org.apache.hadoop.classification.InterfaceStability; import org.apache.hadoop.hdfs.server.datanode.DataNode; @@ -33,9 +32,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; +import java.util.HashMap; import java.util.Iterator; import java.util.Map; -import java.util.Set; /** * This class detects and maintains DataNode disk outliers and their @@ -122,43 +121,41 @@ public class DataNodeDiskMetrics { private void detectAndUpdateDiskOutliers(Map metadataOpStats, Map readIoStats, Map writeIoStats) { - Set diskOutliersSet = Sets.newHashSet(); + Map> diskStats = Maps.newHashMap(); // Get MetadataOp Outliers Map metadataOpOutliers = slowDiskDetector .getOutliers(metadataOpStats); - if (!metadataOpOutliers.isEmpty()) { - diskOutliersSet.addAll(metadataOpOutliers.keySet()); + for (Map.Entry entry : metadataOpOutliers.entrySet()) { + addDiskStat(diskStats, entry.getKey(), DiskOp.METADATA, entry.getValue()); } // Get ReadIo Outliers Map readIoOutliers = slowDiskDetector .getOutliers(readIoStats); - if (!readIoOutliers.isEmpty()) { - diskOutliersSet.addAll(readIoOutliers.keySet()); + for (Map.Entry entry : readIoOutliers.entrySet()) { + addDiskStat(diskStats, entry.getKey(), DiskOp.READ, entry.getValue()); } // Get WriteIo Outliers Map writeIoOutliers = slowDiskDetector .getOutliers(writeIoStats); - if (!readIoOutliers.isEmpty()) { - diskOutliersSet.addAll(writeIoOutliers.keySet()); - } - - Map> diskStats = - Maps.newHashMap(); - for (String disk : diskOutliersSet) { - Map diskStat = Maps.newHashMap(); - diskStat.put(DiskOp.METADATA, metadataOpStats.get(disk)); - diskStat.put(DiskOp.READ, readIoStats.get(disk)); - diskStat.put(DiskOp.WRITE, writeIoStats.get(disk)); - diskStats.put(disk, diskStat); + for (Map.Entry entry : writeIoOutliers.entrySet()) { + addDiskStat(diskStats, entry.getKey(), DiskOp.WRITE, entry.getValue()); } diskOutliersStats = diskStats; LOG.debug("Updated disk outliers."); } + private void addDiskStat(Map> diskStats, + String disk, DiskOp diskOp, double latency) { + if (!diskStats.containsKey(disk)) { + diskStats.put(disk, new HashMap<>()); + } + diskStats.get(disk).put(diskOp, latency); + } + public Map> getDiskOutliersStats() { return diskOutliersStats; } diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/blockmanagement/TestSlowDiskTracker.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/blockmanagement/TestSlowDiskTracker.java new file mode 100644 index 00000000000..e96b96a25af --- /dev/null +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/blockmanagement/TestSlowDiskTracker.java @@ -0,0 +1,448 @@ +/** + * 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.blockmanagement; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.ImmutableMap; +import org.apache.hadoop.conf.Configuration; +import static org.apache.hadoop.hdfs.DFSConfigKeys + .DFS_DATANODE_FILEIO_PROFILING_SAMPLING_FRACTION_KEY; +import static org.apache.hadoop.hdfs.DFSConfigKeys + .DFS_DATANODE_OUTLIERS_REPORT_INTERVAL_KEY; +import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_HEARTBEAT_INTERVAL_KEY; +import org.apache.hadoop.hdfs.HdfsConfiguration; +import org.apache.hadoop.hdfs.MiniDFSCluster; +import org.apache.hadoop.hdfs.server.datanode.DataNode; +import org.apache.hadoop.hdfs.server.namenode.NameNode; +import org.apache.hadoop.hdfs.server.protocol.SlowDiskReports; +import org.apache.hadoop.hdfs.server.protocol.SlowDiskReports.DiskOp; +import org.apache.hadoop.hdfs.server.blockmanagement.SlowDiskTracker + .DiskLatency; +import org.apache.hadoop.test.GenericTestUtils; +import org.apache.hadoop.util.FakeTimer; + +import com.google.common.base.Supplier; +import com.google.common.collect.Maps; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.Timeout; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * Tests for {@link SlowDiskTracker}. + */ +public class TestSlowDiskTracker { + public static final Logger LOG = LoggerFactory.getLogger( + TestSlowDiskTracker.class); + + /** + * Set a timeout for every test case. + */ + @Rule + public Timeout testTimeout = new Timeout(300_000); + + private static Configuration conf; + private SlowDiskTracker tracker; + private FakeTimer timer; + private long reportValidityMs; + private static final long OUTLIERS_REPORT_INTERVAL = 1000; + + static { + conf = new HdfsConfiguration(); + conf.setLong(DFS_HEARTBEAT_INTERVAL_KEY, 1L); + conf.setDouble(DFS_DATANODE_FILEIO_PROFILING_SAMPLING_FRACTION_KEY, 1.0); + conf.setTimeDuration(DFS_DATANODE_OUTLIERS_REPORT_INTERVAL_KEY, + OUTLIERS_REPORT_INTERVAL, TimeUnit.MILLISECONDS); + } + @Before + public void setup() { + timer = new FakeTimer(); + tracker = new SlowDiskTracker(conf, timer); + reportValidityMs = tracker.getReportValidityMs(); + } + + @Test + public void testDataNodeHeartbeatSlowDiskReport() throws Exception { + MiniDFSCluster cluster = new MiniDFSCluster.Builder(conf).numDataNodes(2) + .build(); + try { + DataNode dn1 = cluster.getDataNodes().get(0); + DataNode dn2 = cluster.getDataNodes().get(1); + NameNode nn = cluster.getNameNode(0); + + DatanodeManager datanodeManager = nn.getNamesystem().getBlockManager() + .getDatanodeManager(); + SlowDiskTracker slowDiskTracker = datanodeManager.getSlowDiskTracker(); + slowDiskTracker.setReportValidityMs(OUTLIERS_REPORT_INTERVAL * 100); + + dn1.getDiskMetrics().addSlowDiskForTesting("disk1", ImmutableMap.of( + DiskOp.WRITE, 1.3)); + dn1.getDiskMetrics().addSlowDiskForTesting("disk2", ImmutableMap.of( + DiskOp.READ, 1.6, DiskOp.WRITE, 1.1)); + dn2.getDiskMetrics().addSlowDiskForTesting("disk1", ImmutableMap.of( + DiskOp.METADATA, 0.8)); + dn2.getDiskMetrics().addSlowDiskForTesting("disk2", ImmutableMap.of( + DiskOp.WRITE, 1.3)); + + String dn1ID = dn1.getDatanodeId().getIpcAddr(false); + String dn2ID = dn2.getDatanodeId().getIpcAddr(false); + + // Advance the timer and wait for NN to receive reports from DataNodes. + Thread.sleep(OUTLIERS_REPORT_INTERVAL); + + // Wait for NN to receive reports from all DNs + GenericTestUtils.waitFor(new Supplier() { + @Override + public Boolean get() { + return (slowDiskTracker.getSlowDisksReport().size() == 4); + } + }, 1000, 100000); + + Map slowDisksReport = getSlowDisksReportForTesting( + slowDiskTracker); + + assertThat(slowDisksReport.size(), is(4)); + assertTrue(Math.abs(slowDisksReport.get(dn1ID + ":disk1") + .getLatency(DiskOp.WRITE) - 1.3) < 0.0000001); + assertTrue(Math.abs(slowDisksReport.get(dn1ID + ":disk2") + .getLatency(DiskOp.READ) - 1.6) < 0.0000001); + assertTrue(Math.abs(slowDisksReport.get(dn1ID + ":disk2") + .getLatency(DiskOp.WRITE) - 1.1) < 0.0000001); + assertTrue(Math.abs(slowDisksReport.get(dn2ID + ":disk1") + .getLatency(DiskOp.METADATA) - 0.8) < 0.0000001); + assertTrue(Math.abs(slowDisksReport.get(dn2ID + ":disk2") + .getLatency(DiskOp.WRITE) - 1.3) < 0.0000001); + + // Test the slow disk report JSON string + ArrayList jsonReport = getAndDeserializeJson( + slowDiskTracker.getSlowDiskReportAsJsonString()); + + assertThat(jsonReport.size(), is(4)); + assertTrue(isDiskInReports(jsonReport, dn1ID, "disk1", DiskOp.WRITE, 1.3)); + assertTrue(isDiskInReports(jsonReport, dn1ID, "disk2", DiskOp.READ, 1.6)); + assertTrue(isDiskInReports(jsonReport, dn1ID, "disk2", DiskOp.WRITE, 1.1)); + assertTrue(isDiskInReports(jsonReport, dn2ID, "disk1", DiskOp.METADATA, + 0.8)); + assertTrue(isDiskInReports(jsonReport, dn2ID, "disk2", DiskOp.WRITE, 1.3)); + } finally { + cluster.shutdown(); + } + } + + /** + * Edge case, there are no reports to retrieve. + */ + @Test + public void testEmptyReports() { + tracker.updateSlowDiskReportAsync(timer.monotonicNow()); + assertTrue(getSlowDisksReportForTesting(tracker).isEmpty()); + } + + @Test + public void testReportsAreRetrieved() throws Exception { + addSlowDiskForTesting("dn1", "disk1", + ImmutableMap.of(DiskOp.METADATA, 1.1, DiskOp.READ, 1.8)); + addSlowDiskForTesting("dn1", "disk2", + ImmutableMap.of(DiskOp.READ, 1.3)); + addSlowDiskForTesting("dn2", "disk2", + ImmutableMap.of(DiskOp.READ, 1.1)); + + tracker.updateSlowDiskReportAsync(timer.monotonicNow()); + + GenericTestUtils.waitFor(new Supplier() { + @Override + public Boolean get() { + return !tracker.getSlowDisksReport().isEmpty(); + } + }, 500, 5000); + + Map reports = getSlowDisksReportForTesting(tracker); + + assertThat(reports.size(), is(3)); + assertTrue(Math.abs(reports.get("dn1:disk1") + .getLatency(DiskOp.METADATA) - 1.1) < 0.0000001); + assertTrue(Math.abs(reports.get("dn1:disk1") + .getLatency(DiskOp.READ) - 1.8) < 0.0000001); + assertTrue(Math.abs(reports.get("dn1:disk2") + .getLatency(DiskOp.READ) - 1.3) < 0.0000001); + assertTrue(Math.abs(reports.get("dn2:disk2") + .getLatency(DiskOp.READ) - 1.1) < 0.0000001); + } + + /** + * Test that when all reports are expired, we get back nothing. + */ + @Test + public void testAllReportsAreExpired() throws Exception { + addSlowDiskForTesting("dn1", "disk1", + ImmutableMap.of(DiskOp.METADATA, 1.1, DiskOp.READ, 1.8)); + addSlowDiskForTesting("dn1", "disk2", + ImmutableMap.of(DiskOp.READ, 1.3)); + addSlowDiskForTesting("dn2", "disk2", + ImmutableMap.of(DiskOp.WRITE, 1.1)); + + // No reports should expire after 1ms. + timer.advance(1); + tracker.updateSlowDiskReportAsync(timer.monotonicNow()); + + GenericTestUtils.waitFor(new Supplier() { + @Override + public Boolean get() { + return !tracker.getSlowDisksReport().isEmpty(); + } + }, 500, 5000); + + Map reports = getSlowDisksReportForTesting(tracker); + + assertThat(reports.size(), is(3)); + assertTrue(Math.abs(reports.get("dn1:disk1") + .getLatency(DiskOp.METADATA) - 1.1) < 0.0000001); + assertTrue(Math.abs(reports.get("dn1:disk1") + .getLatency(DiskOp.READ) - 1.8) < 0.0000001); + assertTrue(Math.abs(reports.get("dn1:disk2") + .getLatency(DiskOp.READ) - 1.3) < 0.0000001); + assertTrue(Math.abs(reports.get("dn2:disk2") + .getLatency(DiskOp.WRITE) - 1.1) < 0.0000001); + + // All reports should expire after REPORT_VALIDITY_MS. + timer.advance(reportValidityMs); + tracker.updateSlowDiskReportAsync(timer.monotonicNow()); + + GenericTestUtils.waitFor(new Supplier() { + @Override + public Boolean get() { + return tracker.getSlowDisksReport().isEmpty(); + } + }, 500, 3000); + + reports = getSlowDisksReportForTesting(tracker); + + assertThat(reports.size(), is(0)); + } + + /** + * Test the case when a subset of reports has expired. + * Ensure that we only get back non-expired reports. + */ + @Test + public void testSomeReportsAreExpired() throws Exception { + addSlowDiskForTesting("dn1", "disk1", + ImmutableMap.of(DiskOp.METADATA, 1.1, DiskOp.READ, 1.8)); + addSlowDiskForTesting("dn1", "disk2", + ImmutableMap.of(DiskOp.READ, 1.3)); + timer.advance(reportValidityMs); + addSlowDiskForTesting("dn2", "disk2", + ImmutableMap.of(DiskOp.WRITE, 1.1)); + + tracker.updateSlowDiskReportAsync(timer.monotonicNow()); + + GenericTestUtils.waitFor(new Supplier() { + @Override + public Boolean get() { + return !tracker.getSlowDisksReport().isEmpty(); + } + }, 500, 5000); + + Map reports = getSlowDisksReportForTesting(tracker); + + assertThat(reports.size(), is(1)); + assertTrue(Math.abs(reports.get("dn2:disk2") + .getLatency(DiskOp.WRITE) - 1.1) < 0.0000001); + } + + /** + * Test the case when an expired report is replaced by a valid one. + */ + @Test + public void testReplacement() throws Exception { + addSlowDiskForTesting("dn1", "disk1", + ImmutableMap.of(DiskOp.METADATA, 1.1, DiskOp.READ, 1.8)); + timer.advance(reportValidityMs); + addSlowDiskForTesting("dn1", "disk1", + ImmutableMap.of(DiskOp.READ, 1.4)); + + tracker.updateSlowDiskReportAsync(timer.monotonicNow()); + + GenericTestUtils.waitFor(new Supplier() { + @Override + public Boolean get() { + return !tracker.getSlowDisksReport().isEmpty(); + } + }, 500, 5000); + + Map reports = getSlowDisksReportForTesting(tracker); + + assertThat(reports.size(), is(1)); + assertTrue(reports.get("dn1:disk1").getLatency(DiskOp.METADATA) == null); + assertTrue(Math.abs(reports.get("dn1:disk1") + .getLatency(DiskOp.READ) - 1.4) < 0.0000001); + } + + @Test + public void testGetJson() throws Exception { + addSlowDiskForTesting("dn1", "disk1", + ImmutableMap.of(DiskOp.METADATA, 1.1, DiskOp.READ, 1.8)); + addSlowDiskForTesting("dn1", "disk2", + ImmutableMap.of(DiskOp.READ, 1.3)); + addSlowDiskForTesting("dn2", "disk2", + ImmutableMap.of(DiskOp.WRITE, 1.1)); + addSlowDiskForTesting("dn3", "disk1", + ImmutableMap.of(DiskOp.WRITE, 1.1)); + + tracker.updateSlowDiskReportAsync(timer.monotonicNow()); + + GenericTestUtils.waitFor(new Supplier() { + @Override + public Boolean get() { + return tracker.getSlowDiskReportAsJsonString() != null; + } + }, 500, 5000); + + ArrayList jsonReport = getAndDeserializeJson( + tracker.getSlowDiskReportAsJsonString()); + + // And ensure its contents are what we expect. + assertThat(jsonReport.size(), is(4)); + assertTrue(isDiskInReports(jsonReport, "dn1", "disk1", DiskOp.METADATA, + 1.1)); + assertTrue(isDiskInReports(jsonReport, "dn1", "disk1", DiskOp.READ, 1.8)); + assertTrue(isDiskInReports(jsonReport, "dn1", "disk2", DiskOp.READ, 1.3)); + assertTrue(isDiskInReports(jsonReport, "dn2", "disk2", DiskOp.WRITE, 1.1)); + assertTrue(isDiskInReports(jsonReport, "dn3", "disk1", DiskOp.WRITE, 1.1)); + } + + @Test + public void testGetJsonSizeIsLimited() throws Exception { + addSlowDiskForTesting("dn1", "disk1", + ImmutableMap.of(DiskOp.READ, 1.1)); + addSlowDiskForTesting("dn1", "disk2", + ImmutableMap.of(DiskOp.READ, 1.2)); + addSlowDiskForTesting("dn1", "disk3", + ImmutableMap.of(DiskOp.READ, 1.3)); + addSlowDiskForTesting("dn2", "disk1", + ImmutableMap.of(DiskOp.READ, 1.4)); + addSlowDiskForTesting("dn2", "disk2", + ImmutableMap.of(DiskOp.READ, 1.5)); + addSlowDiskForTesting("dn3", "disk1", + ImmutableMap.of(DiskOp.WRITE, 1.6)); + addSlowDiskForTesting("dn3", "disk2", + ImmutableMap.of(DiskOp.READ, 1.7)); + addSlowDiskForTesting("dn3", "disk3", + ImmutableMap.of(DiskOp.READ, 1.2)); + + tracker.updateSlowDiskReportAsync(timer.monotonicNow()); + + GenericTestUtils.waitFor(new Supplier() { + @Override + public Boolean get() { + return tracker.getSlowDiskReportAsJsonString() != null; + } + }, 500, 5000); + + ArrayList jsonReport = getAndDeserializeJson( + tracker.getSlowDiskReportAsJsonString()); + + // Ensure that only the top 5 highest latencies are in the report. + assertThat(jsonReport.size(), is(5)); + assertTrue(isDiskInReports(jsonReport, "dn3", "disk2", DiskOp.READ, 1.7)); + assertTrue(isDiskInReports(jsonReport, "dn3", "disk1", DiskOp.WRITE, 1.6)); + assertTrue(isDiskInReports(jsonReport, "dn2", "disk2", DiskOp.READ, 1.5)); + assertTrue(isDiskInReports(jsonReport, "dn2", "disk1", DiskOp.READ, 1.4)); + assertTrue(isDiskInReports(jsonReport, "dn1", "disk3", DiskOp.READ, 1.3)); + + // Remaining nodes should be in the list. + assertFalse(isDiskInReports(jsonReport, "dn1", "disk1", DiskOp.READ, 1.1)); + assertFalse(isDiskInReports(jsonReport, "dn1", "disk2", DiskOp.READ, 1.2)); + assertFalse(isDiskInReports(jsonReport, "dn3", "disk3", DiskOp.READ, 1.2)); + } + + @Test + public void testEmptyReport() throws Exception { + addSlowDiskForTesting("dn1", "disk1", + ImmutableMap.of(DiskOp.READ, 1.1)); + timer.advance(reportValidityMs); + + tracker.updateSlowDiskReportAsync(timer.monotonicNow()); + + GenericTestUtils.waitFor(new Supplier() { + @Override + public Boolean get() { + return tracker.getSlowDiskReportAsJsonString() != null; + } + }, 500, 5000); + + ArrayList jsonReport = getAndDeserializeJson( + tracker.getSlowDiskReportAsJsonString()); + + assertTrue(jsonReport.isEmpty()); + } + + private boolean isDiskInReports(ArrayList reports, + String dataNodeID, String disk, DiskOp diskOp, double latency) { + String diskID = SlowDiskTracker.getSlowDiskIDForReport(dataNodeID, disk); + for (DiskLatency diskLatency : reports) { + if (diskLatency.getSlowDiskID().equals(diskID)) { + if (diskLatency.getLatency(diskOp) == null) { + return false; + } + if (Math.abs(diskLatency.getLatency(diskOp) - latency) < 0.0000001) { + return true; + } + } + } + return false; + } + + private ArrayList getAndDeserializeJson( + final String json) throws IOException { + return (new ObjectMapper()).readValue(json, + new TypeReference>() {}); + } + + private void addSlowDiskForTesting(String dnID, String disk, + Map latencies) { + Map> slowDisk = Maps.newHashMap(); + slowDisk.put(disk, latencies); + SlowDiskReports slowDiskReport = SlowDiskReports.create(slowDisk); + tracker.addSlowDiskReport(dnID, slowDiskReport); + } + + Map getSlowDisksReportForTesting( + SlowDiskTracker slowDiskTracker) { + Map slowDisksMap = Maps.newHashMap(); + for (DiskLatency diskLatency : slowDiskTracker.getSlowDisksReport()) { + slowDisksMap.put(diskLatency.getSlowDiskID(), diskLatency); + } + return slowDisksMap; + } +} From 4d1fac5df2508011adfc4c5d683beb00fd5ced45 Mon Sep 17 00:00:00 2001 From: Jason Lowe Date: Fri, 31 Mar 2017 10:05:34 -0500 Subject: [PATCH 175/188] YARN-6411. Clean up the overwrite of createDispatcher() in subclass of MockRM. Contributed by Yufei Gu --- .../v2/app/rm/TestRMContainerAllocator.java | 453 ++++++++---------- .../api/impl/TestAMRMClientOnRMRestart.java | 59 +-- .../server/resourcemanager/ACLsTestBase.java | 10 - .../server/resourcemanager/RMHATestBase.java | 20 +- .../ReservationACLsTestBase.java | 5 +- .../TestApplicationCleanup.java | 44 +- .../TestApplicationMasterLauncher.java | 11 +- .../TestApplicationMasterService.java | 19 +- .../TestNodeBlacklistingOnAMFailures.java | 41 +- .../TestReservationSystemWithRMHA.java | 5 +- .../TestAMRMRPCNodeUpdates.java | 18 +- .../resourcetracker/TestNMReconnect.java | 14 +- .../rmcontainer/TestRMContainerImpl.java | 1 - .../capacity/TestApplicationPriority.java | 29 +- .../security/TestClientToAMTokens.java | 23 +- 15 files changed, 277 insertions(+), 475 deletions(-) diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/test/java/org/apache/hadoop/mapreduce/v2/app/rm/TestRMContainerAllocator.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/test/java/org/apache/hadoop/mapreduce/v2/app/rm/TestRMContainerAllocator.java index e6aee6ea997..933bd013ef6 100644 --- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/test/java/org/apache/hadoop/mapreduce/v2/app/rm/TestRMContainerAllocator.java +++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/test/java/org/apache/hadoop/mapreduce/v2/app/rm/TestRMContainerAllocator.java @@ -179,21 +179,19 @@ public class TestRMContainerAllocator { Configuration conf = new Configuration(); MyResourceManager rm = new MyResourceManager(conf); rm.start(); - DrainDispatcher dispatcher = (DrainDispatcher) rm.getRMContext() - .getDispatcher(); // Submit the application RMApp app = rm.submitApp(1024); - dispatcher.await(); + rm.drainEvents(); MockNM amNodeManager = rm.registerNode("amNM:1234", 2048); amNodeManager.nodeHeartbeat(true); - dispatcher.await(); + rm.drainEvents(); ApplicationAttemptId appAttemptId = app.getCurrentAppAttempt() .getAppAttemptId(); rm.sendAMLaunched(appAttemptId); - dispatcher.await(); + rm.drainEvents(); JobId jobId = MRBuilderUtils.newJobId(appAttemptId.getApplicationId(), 0); Job mockJob = mock(Job.class); @@ -207,7 +205,7 @@ public class TestRMContainerAllocator { MockNM nodeManager1 = rm.registerNode("h1:1234", 10240); MockNM nodeManager2 = rm.registerNode("h2:1234", 10240); MockNM nodeManager3 = rm.registerNode("h3:1234", 10240); - dispatcher.await(); + rm.drainEvents(); // create the container request ContainerRequestEvent event1 = createReq(jobId, 1, 1024, @@ -222,7 +220,7 @@ public class TestRMContainerAllocator { // this tells the scheduler about the requests // as nodes are not added, no allocations List assigned = allocator.schedule(); - dispatcher.await(); + rm.drainEvents(); Assert.assertEquals("No of assignments must be 0", 0, assigned.size()); Assert.assertEquals(4, rm.getMyFifoScheduler().lastAsk.size()); @@ -234,7 +232,7 @@ public class TestRMContainerAllocator { // this tells the scheduler about the requests // as nodes are not added, no allocations assigned = allocator.schedule(); - dispatcher.await(); + rm.drainEvents(); Assert.assertEquals("No of assignments must be 0", 0, assigned.size()); Assert.assertEquals(3, rm.getMyFifoScheduler().lastAsk.size()); @@ -242,18 +240,18 @@ public class TestRMContainerAllocator { nodeManager1.nodeHeartbeat(true); // Node heartbeat nodeManager2.nodeHeartbeat(true); // Node heartbeat nodeManager3.nodeHeartbeat(true); // Node heartbeat - dispatcher.await(); + rm.drainEvents(); assigned = allocator.schedule(); - dispatcher.await(); + rm.drainEvents(); Assert.assertEquals(0, rm.getMyFifoScheduler().lastAsk.size()); checkAssignments(new ContainerRequestEvent[] { event1, event2, event3 }, assigned, false); // check that the assigned container requests are cancelled - assigned = allocator.schedule(); - dispatcher.await(); - Assert.assertEquals(5, rm.getMyFifoScheduler().lastAsk.size()); + allocator.schedule(); + rm.drainEvents(); + Assert.assertEquals(5, rm.getMyFifoScheduler().lastAsk.size()); } @Test @@ -269,21 +267,19 @@ public class TestRMContainerAllocator { Configuration conf = new Configuration(); MyResourceManager rm = new MyResourceManager(conf); rm.start(); - DrainDispatcher dispatcher = (DrainDispatcher) rm.getRMContext() - .getDispatcher(); // Submit the application RMApp app = rm.submitApp(1024); - dispatcher.await(); + rm.drainEvents(); MockNM amNodeManager = rm.registerNode("amNM:1234", 2048); amNodeManager.nodeHeartbeat(true); - dispatcher.await(); + rm.drainEvents(); ApplicationAttemptId appAttemptId = app.getCurrentAppAttempt() .getAppAttemptId(); rm.sendAMLaunched(appAttemptId); - dispatcher.await(); + rm.drainEvents(); JobId jobId = MRBuilderUtils.newJobId(appAttemptId.getApplicationId(), 0); Job mockJob = mock(Job.class); @@ -297,7 +293,7 @@ public class TestRMContainerAllocator { MockNM nodeManager1 = rm.registerNode("h1:1234", 3072); // can assign 2 maps rm.registerNode("h2:1234", 10240); // wont heartbeat on node local node MockNM nodeManager3 = rm.registerNode("h3:1234", 1536); // assign 1 map - dispatcher.await(); + rm.drainEvents(); // create the container requests for maps ContainerRequestEvent event1 = createReq(jobId, 1, 1024, @@ -313,7 +309,7 @@ public class TestRMContainerAllocator { // this tells the scheduler about the requests // as nodes are not added, no allocations List assigned = allocator.schedule(); - dispatcher.await(); + rm.drainEvents(); Assert.assertEquals("No of assignments must be 0", 0, assigned.size()); // update resources in scheduler @@ -323,10 +319,10 @@ public class TestRMContainerAllocator { // Node heartbeat from node-local next. This allocates 2 node local // containers for task1 and task2. These should be matched with those tasks. nodeManager1.nodeHeartbeat(true); - dispatcher.await(); + rm.drainEvents(); assigned = allocator.schedule(); - dispatcher.await(); + rm.drainEvents(); checkAssignments(new ContainerRequestEvent[] { event1, event2, event3 }, assigned, false); // remove the rack-local assignment that should have happened for task3 @@ -350,21 +346,19 @@ public class TestRMContainerAllocator { Configuration conf = new Configuration(); MyResourceManager rm = new MyResourceManager(conf); rm.start(); - DrainDispatcher dispatcher = (DrainDispatcher) rm.getRMContext() - .getDispatcher(); // Submit the application RMApp app = rm.submitApp(1024); - dispatcher.await(); + rm.drainEvents(); MockNM amNodeManager = rm.registerNode("amNM:1234", 2048); amNodeManager.nodeHeartbeat(true); - dispatcher.await(); + rm.drainEvents(); ApplicationAttemptId appAttemptId = app.getCurrentAppAttempt() .getAppAttemptId(); rm.sendAMLaunched(appAttemptId); - dispatcher.await(); + rm.drainEvents(); JobId jobId = MRBuilderUtils.newJobId(appAttemptId.getApplicationId(), 0); Job mockJob = mock(Job.class); @@ -378,7 +372,7 @@ public class TestRMContainerAllocator { MockNM nodeManager1 = rm.registerNode("h1:1234", 10240); MockNM nodeManager2 = rm.registerNode("h2:1234", 10240); MockNM nodeManager3 = rm.registerNode("h3:1234", 10240); - dispatcher.await(); + rm.drainEvents(); // create the container request ContainerRequestEvent event1 = createReq(jobId, 1, 1024, @@ -393,17 +387,17 @@ public class TestRMContainerAllocator { // this tells the scheduler about the requests // as nodes are not added, no allocations List assigned = allocator.schedule(); - dispatcher.await(); + rm.drainEvents(); Assert.assertEquals("No of assignments must be 0", 0, assigned.size()); // update resources in scheduler nodeManager1.nodeHeartbeat(true); // Node heartbeat nodeManager2.nodeHeartbeat(true); // Node heartbeat nodeManager3.nodeHeartbeat(true); // Node heartbeat - dispatcher.await(); + rm.drainEvents(); assigned = allocator.schedule(); - dispatcher.await(); + rm.drainEvents(); checkAssignments(new ContainerRequestEvent[] { event1, event2 }, assigned, false); } @@ -416,19 +410,17 @@ public class TestRMContainerAllocator { conf.setFloat(MRJobConfig.COMPLETED_MAPS_FOR_REDUCE_SLOWSTART, 0.0f); final MyResourceManager rm = new MyResourceManager(conf); rm.start(); - final DrainDispatcher dispatcher = (DrainDispatcher) rm.getRMContext() - .getDispatcher(); final RMApp app = rm.submitApp(1024); - dispatcher.await(); + rm.drainEvents(); final String host = "host1"; final MockNM nm = rm.registerNode(String.format("%s:1234", host), 2048); nm.nodeHeartbeat(true); - dispatcher.await(); + rm.drainEvents(); final ApplicationAttemptId appAttemptId = app.getCurrentAppAttempt() .getAppAttemptId(); rm.sendAMLaunched(appAttemptId); - dispatcher.await(); + rm.drainEvents(); final JobId jobId = MRBuilderUtils .newJobId(appAttemptId.getApplicationId(), 0); final Job mockJob = mock(Job.class); @@ -438,20 +430,20 @@ public class TestRMContainerAllocator { final MyContainerAllocator allocator = new MyContainerAllocator(rm, conf, appAttemptId, mockJob, SystemClock.getInstance()); // add resources to scheduler - dispatcher.await(); + rm.drainEvents(); // create the container request final String[] locations = new String[] { host }; allocator.sendRequest(createReq(jobId, 0, 1024, locations, false, true)); for (int i = 0; i < 1;) { - dispatcher.await(); + rm.drainEvents(); i += allocator.schedule().size(); nm.nodeHeartbeat(true); } allocator.sendRequest(createReq(jobId, 0, 1024, locations, true, false)); while (allocator.getTaskAttemptKillEvents().size() == 0) { - dispatcher.await(); + rm.drainEvents(); allocator.schedule().size(); nm.nodeHeartbeat(true); } @@ -468,21 +460,19 @@ public class TestRMContainerAllocator { Configuration conf = new Configuration(); MyResourceManager rm = new MyResourceManager(conf); rm.start(); - DrainDispatcher dispatcher = (DrainDispatcher) rm.getRMContext() - .getDispatcher(); // Submit the application RMApp app = rm.submitApp(1024); - dispatcher.await(); + rm.drainEvents(); MockNM amNodeManager = rm.registerNode("amNM:1234", 2048); amNodeManager.nodeHeartbeat(true); - dispatcher.await(); + rm.drainEvents(); ApplicationAttemptId appAttemptId = app.getCurrentAppAttempt() .getAppAttemptId(); rm.sendAMLaunched(appAttemptId); - dispatcher.await(); + rm.drainEvents(); JobId jobId = MRBuilderUtils.newJobId(appAttemptId.getApplicationId(), 0); Job mockJob = mock(Job.class); @@ -521,21 +511,19 @@ public class TestRMContainerAllocator { MyResourceManager rm = new MyResourceManager(conf); rm.start(); - DrainDispatcher dispatcher = (DrainDispatcher) rm.getRMContext() - .getDispatcher(); // Submit the application RMApp app = rm.submitApp(1024); - dispatcher.await(); + rm.drainEvents(); MockNM amNodeManager = rm.registerNode("amNM:1234", 2048); amNodeManager.nodeHeartbeat(true); - dispatcher.await(); + rm.drainEvents(); ApplicationAttemptId appAttemptId = app.getCurrentAppAttempt() .getAppAttemptId(); rm.sendAMLaunched(appAttemptId); - dispatcher.await(); + rm.drainEvents(); JobId jobId = MRBuilderUtils.newJobId(appAttemptId.getApplicationId(), 0); Job mockJob = mock(Job.class); @@ -584,21 +572,19 @@ public class TestRMContainerAllocator { MyResourceManager rm = new MyResourceManager(conf); rm.start(); rm.getMyFifoScheduler().forceResourceLimit(Resource.newInstance(8192, 8)); - DrainDispatcher dispatcher = (DrainDispatcher) rm.getRMContext() - .getDispatcher(); // Submit the application RMApp app = rm.submitApp(1024); - dispatcher.await(); + rm.drainEvents(); MockNM amNodeManager = rm.registerNode("amNM:1234", 2048); amNodeManager.nodeHeartbeat(true); - dispatcher.await(); + rm.drainEvents(); ApplicationAttemptId appAttemptId = app.getCurrentAppAttempt() .getAppAttemptId(); rm.sendAMLaunched(appAttemptId); - dispatcher.await(); + rm.drainEvents(); JobId jobId = MRBuilderUtils.newJobId(appAttemptId.getApplicationId(), 0); Job mockJob = mock(Job.class); @@ -639,18 +625,16 @@ public class TestRMContainerAllocator { conf.setFloat(MRJobConfig.COMPLETED_MAPS_FOR_REDUCE_SLOWSTART, 0.0f); final MyResourceManager2 rm = new MyResourceManager2(conf); rm.start(); - final DrainDispatcher dispatcher = (DrainDispatcher)rm.getRMContext() - .getDispatcher(); final RMApp app = rm.submitApp(2048); - dispatcher.await(); + rm.drainEvents(); final String host = "host1"; final MockNM nm = rm.registerNode(String.format("%s:1234", host), 4096); nm.nodeHeartbeat(true); - dispatcher.await(); + rm.drainEvents(); final ApplicationAttemptId appAttemptId = app.getCurrentAppAttempt() .getAppAttemptId(); rm.sendAMLaunched(appAttemptId); - dispatcher.await(); + rm.drainEvents(); final JobId jobId = MRBuilderUtils .newJobId(appAttemptId.getApplicationId(), 0); final Job mockJob = mock(Job.class); @@ -666,14 +650,14 @@ public class TestRMContainerAllocator { allocator.scheduleAllReduces(); allocator.makeRemoteRequest(); nm.nodeHeartbeat(true); - dispatcher.await(); + rm.drainEvents(); allocator.sendRequest(createReq(jobId, 1, 1024, locations, false, false)); int assignedContainer; for (assignedContainer = 0; assignedContainer < 1;) { assignedContainer += allocator.schedule().size(); nm.nodeHeartbeat(true); - dispatcher.await(); + rm.drainEvents(); } // only 1 allocated container should be assigned Assert.assertEquals(assignedContainer, 1); @@ -773,21 +757,19 @@ public class TestRMContainerAllocator { Configuration conf = new Configuration(); MyResourceManager rm = new MyResourceManager(conf); rm.start(); - DrainDispatcher dispatcher = (DrainDispatcher) rm.getRMContext() - .getDispatcher(); // Submit the application RMApp app = rm.submitApp(1024); - dispatcher.await(); + rm.drainEvents(); MockNM amNodeManager = rm.registerNode("amNM:1234", 2048); amNodeManager.nodeHeartbeat(true); - dispatcher.await(); + rm.drainEvents(); ApplicationAttemptId appAttemptId = app.getCurrentAppAttempt() .getAppAttemptId(); rm.sendAMLaunched(appAttemptId); - dispatcher.await(); + rm.drainEvents(); JobId jobId = MRBuilderUtils.newJobId(appAttemptId.getApplicationId(), 0); Job mockJob = mock(Job.class); @@ -801,7 +783,7 @@ public class TestRMContainerAllocator { MockNM nodeManager1 = rm.registerNode("h1:1234", 1024); MockNM nodeManager2 = rm.registerNode("h2:1234", 10240); MockNM nodeManager3 = rm.registerNode("h3:1234", 10240); - dispatcher.await(); + rm.drainEvents(); // create the container request // send MAP request @@ -822,17 +804,17 @@ public class TestRMContainerAllocator { // this tells the scheduler about the requests // as nodes are not added, no allocations List assigned = allocator.schedule(); - dispatcher.await(); + rm.drainEvents(); Assert.assertEquals("No of assignments must be 0", 0, assigned.size()); // update resources in scheduler nodeManager1.nodeHeartbeat(true); // Node heartbeat nodeManager2.nodeHeartbeat(true); // Node heartbeat nodeManager3.nodeHeartbeat(true); // Node heartbeat - dispatcher.await(); + rm.drainEvents(); assigned = allocator.schedule(); - dispatcher.await(); + rm.drainEvents(); checkAssignments(new ContainerRequestEvent[] { event1, event3 }, assigned, false); @@ -863,11 +845,6 @@ public class TestRMContainerAllocator { MyResourceManager.setClusterTimeStamp(fakeClusterTimeStamp); } - @Override - protected Dispatcher createDispatcher() { - return new DrainDispatcher(); - } - @Override protected EventHandler createSchedulerEventDispatcher() { // Dispatch inline for test sanity @@ -912,16 +889,16 @@ public class TestRMContainerAllocator { // Submit the application RMApp rmApp = rm.submitApp(1024); - rmDispatcher.await(); + rm.drainEvents(); MockNM amNodeManager = rm.registerNode("amNM:1234", 21504); amNodeManager.nodeHeartbeat(true); - rmDispatcher.await(); + rm.drainEvents(); final ApplicationAttemptId appAttemptId = rmApp.getCurrentAppAttempt() .getAppAttemptId(); rm.sendAMLaunched(appAttemptId); - rmDispatcher.await(); + rm.drainEvents(); MRApp mrApp = new MRApp(appAttemptId, ContainerId.newContainerId( appAttemptId, 0), 10, 10, false, this.getClass().getName(), true, 1) { @@ -959,11 +936,11 @@ public class TestRMContainerAllocator { amDispatcher.await(); allocator.schedule(); - rmDispatcher.await(); + rm.drainEvents(); amNodeManager.nodeHeartbeat(true); - rmDispatcher.await(); + rm.drainEvents(); allocator.schedule(); - rmDispatcher.await(); + rm.drainEvents(); // Wait for all map-tasks to be running for (Task t : job.getTasks().values()) { @@ -973,7 +950,7 @@ public class TestRMContainerAllocator { } allocator.schedule(); // Send heartbeat - rmDispatcher.await(); + rm.drainEvents(); Assert.assertEquals(0.05f, job.getProgress(), 0.001f); Assert.assertEquals(0.05f, rmApp.getProgress(), 0.001f); @@ -981,14 +958,14 @@ public class TestRMContainerAllocator { Iterator it = job.getTasks().values().iterator(); finishNextNTasks(rmDispatcher, amNodeManager, mrApp, it, 1); allocator.schedule(); - rmDispatcher.await(); + rm.drainEvents(); Assert.assertEquals(0.095f, job.getProgress(), 0.001f); Assert.assertEquals(0.095f, rmApp.getProgress(), 0.001f); // Finish off 7 more so that map-progress is 80% finishNextNTasks(rmDispatcher, amNodeManager, mrApp, it, 7); allocator.schedule(); - rmDispatcher.await(); + rm.drainEvents(); Assert.assertEquals(0.41f, job.getProgress(), 0.001f); Assert.assertEquals(0.41f, rmApp.getProgress(), 0.001f); @@ -996,11 +973,11 @@ public class TestRMContainerAllocator { finishNextNTasks(rmDispatcher, amNodeManager, mrApp, it, 2); allocator.schedule(); - rmDispatcher.await(); + rm.drainEvents(); amNodeManager.nodeHeartbeat(true); - rmDispatcher.await(); + rm.drainEvents(); allocator.schedule(); - rmDispatcher.await(); + rm.drainEvents(); // Wait for all reduce-tasks to be running for (Task t : job.getTasks().values()) { @@ -1013,14 +990,14 @@ public class TestRMContainerAllocator { finishNextNTasks(rmDispatcher, amNodeManager, mrApp, it, 2); allocator.schedule(); - rmDispatcher.await(); + rm.drainEvents(); Assert.assertEquals(0.59f, job.getProgress(), 0.001f); Assert.assertEquals(0.59f, rmApp.getProgress(), 0.001f); // Finish off the remaining 8 reduces. finishNextNTasks(rmDispatcher, amNodeManager, mrApp, it, 8); allocator.schedule(); - rmDispatcher.await(); + rm.drainEvents(); // Remaining is JobCleanup Assert.assertEquals(0.95f, job.getProgress(), 0.001f); Assert.assertEquals(0.95f, rmApp.getProgress(), 0.001f); @@ -1064,16 +1041,16 @@ public class TestRMContainerAllocator { // Submit the application RMApp rmApp = rm.submitApp(1024); - rmDispatcher.await(); + rm.drainEvents(); MockNM amNodeManager = rm.registerNode("amNM:1234", 11264); amNodeManager.nodeHeartbeat(true); - rmDispatcher.await(); + rm.drainEvents(); final ApplicationAttemptId appAttemptId = rmApp.getCurrentAppAttempt() .getAppAttemptId(); rm.sendAMLaunched(appAttemptId); - rmDispatcher.await(); + rm.drainEvents(); MRApp mrApp = new MRApp(appAttemptId, ContainerId.newContainerId( appAttemptId, 0), 10, 0, false, this.getClass().getName(), true, 1) { @@ -1109,11 +1086,11 @@ public class TestRMContainerAllocator { amDispatcher.await(); allocator.schedule(); - rmDispatcher.await(); + rm.drainEvents(); amNodeManager.nodeHeartbeat(true); - rmDispatcher.await(); + rm.drainEvents(); allocator.schedule(); - rmDispatcher.await(); + rm.drainEvents(); // Wait for all map-tasks to be running for (Task t : job.getTasks().values()) { @@ -1121,7 +1098,7 @@ public class TestRMContainerAllocator { } allocator.schedule(); // Send heartbeat - rmDispatcher.await(); + rm.drainEvents(); Assert.assertEquals(0.05f, job.getProgress(), 0.001f); Assert.assertEquals(0.05f, rmApp.getProgress(), 0.001f); @@ -1130,21 +1107,21 @@ public class TestRMContainerAllocator { // Finish off 1 map so that map-progress is 10% finishNextNTasks(rmDispatcher, amNodeManager, mrApp, it, 1); allocator.schedule(); - rmDispatcher.await(); + rm.drainEvents(); Assert.assertEquals(0.14f, job.getProgress(), 0.001f); Assert.assertEquals(0.14f, rmApp.getProgress(), 0.001f); // Finish off 5 more map so that map-progress is 60% finishNextNTasks(rmDispatcher, amNodeManager, mrApp, it, 5); allocator.schedule(); - rmDispatcher.await(); + rm.drainEvents(); Assert.assertEquals(0.59f, job.getProgress(), 0.001f); Assert.assertEquals(0.59f, rmApp.getProgress(), 0.001f); // Finish off remaining map so that map-progress is 100% finishNextNTasks(rmDispatcher, amNodeManager, mrApp, it, 4); allocator.schedule(); - rmDispatcher.await(); + rm.drainEvents(); Assert.assertEquals(0.95f, job.getProgress(), 0.001f); Assert.assertEquals(0.95f, rmApp.getProgress(), 0.001f); } @@ -1154,21 +1131,19 @@ public class TestRMContainerAllocator { Configuration conf = new Configuration(); MyResourceManager rm = new MyResourceManager(conf); rm.start(); - DrainDispatcher dispatcher = (DrainDispatcher) rm.getRMContext() - .getDispatcher(); // Submit the application RMApp app = rm.submitApp(1024); - dispatcher.await(); + rm.drainEvents(); MockNM amNodeManager = rm.registerNode("amNM:1234", 2048); amNodeManager.nodeHeartbeat(true); - dispatcher.await(); + rm.drainEvents(); ApplicationAttemptId appAttemptId = app.getCurrentAppAttempt() .getAppAttemptId(); rm.sendAMLaunched(appAttemptId); - dispatcher.await(); - + rm.drainEvents(); + JobId jobId = MRBuilderUtils.newJobId(appAttemptId.getApplicationId(), 0); Job mockJob = mock(Job.class); MyContainerAllocator allocator = new MyContainerAllocator(rm, conf, @@ -1177,7 +1152,7 @@ public class TestRMContainerAllocator { // add resources to scheduler MockNM nm1 = rm.registerNode("h1:1234", 10240); MockNM nm2 = rm.registerNode("h2:1234", 10240); - dispatcher.await(); + rm.drainEvents(); // create the map container request ContainerRequestEvent event = createReq(jobId, 1, 1024, @@ -1193,16 +1168,16 @@ public class TestRMContainerAllocator { // this tells the scheduler about the requests List assigned = allocator.schedule(); - dispatcher.await(); + rm.drainEvents(); nm1.nodeHeartbeat(true); - dispatcher.await(); + rm.drainEvents(); Assert.assertEquals(1, allocator.getJobUpdatedNodeEvents().size()); Assert.assertEquals(3, allocator.getJobUpdatedNodeEvents().get(0).getUpdatedNodes().size()); allocator.getJobUpdatedNodeEvents().clear(); // get the assignment assigned = allocator.schedule(); - dispatcher.await(); + rm.drainEvents(); Assert.assertEquals(1, assigned.size()); Assert.assertEquals(nm1.getNodeId(), assigned.get(0).getContainer().getNodeId()); // no updated nodes reported @@ -1212,11 +1187,11 @@ public class TestRMContainerAllocator { // mark nodes bad nm1.nodeHeartbeat(false); nm2.nodeHeartbeat(false); - dispatcher.await(); - + rm.drainEvents(); + // schedule response returns updated nodes assigned = allocator.schedule(); - dispatcher.await(); + rm.drainEvents(); Assert.assertEquals(0, assigned.size()); // updated nodes are reported Assert.assertEquals(1, allocator.getJobUpdatedNodeEvents().size()); @@ -1227,7 +1202,7 @@ public class TestRMContainerAllocator { allocator.getTaskAttemptKillEvents().clear(); assigned = allocator.schedule(); - dispatcher.await(); + rm.drainEvents(); Assert.assertEquals(0, assigned.size()); // no updated nodes reported Assert.assertTrue(allocator.getJobUpdatedNodeEvents().isEmpty()); @@ -1247,21 +1222,19 @@ public class TestRMContainerAllocator { MyResourceManager rm = new MyResourceManager(conf); rm.start(); - DrainDispatcher dispatcher = (DrainDispatcher) rm.getRMContext() - .getDispatcher(); // Submit the application RMApp app = rm.submitApp(1024); - dispatcher.await(); + rm.drainEvents(); MockNM amNodeManager = rm.registerNode("amNM:1234", 2048); amNodeManager.nodeHeartbeat(true); - dispatcher.await(); + rm.drainEvents(); ApplicationAttemptId appAttemptId = app.getCurrentAppAttempt() .getAppAttemptId(); rm.sendAMLaunched(appAttemptId); - dispatcher.await(); + rm.drainEvents(); JobId jobId = MRBuilderUtils.newJobId(appAttemptId.getApplicationId(), 0); Job mockJob = mock(Job.class); @@ -1275,7 +1248,7 @@ public class TestRMContainerAllocator { MockNM nodeManager1 = rm.registerNode("h1:1234", 10240); MockNM nodeManager2 = rm.registerNode("h2:1234", 10240); MockNM nodeManager3 = rm.registerNode("h3:1234", 10240); - dispatcher.await(); + rm.drainEvents(); // create the container request ContainerRequestEvent event1 = createReq(jobId, 1, 1024, @@ -1295,7 +1268,7 @@ public class TestRMContainerAllocator { // this tells the scheduler about the requests // as nodes are not added, no allocations List assigned = allocator.schedule(); - dispatcher.await(); + rm.drainEvents(); Assert.assertEquals("No of assignments must be 0", 0, assigned.size()); // Send events to blacklist nodes h1 and h2 @@ -1307,28 +1280,28 @@ public class TestRMContainerAllocator { // update resources in scheduler nodeManager1.nodeHeartbeat(true); // Node heartbeat nodeManager2.nodeHeartbeat(true); // Node heartbeat - dispatcher.await(); + rm.drainEvents(); assigned = allocator.schedule(); Assert.assertEquals("No of assignments must be 0", 0, assigned.size()); - dispatcher.await(); + rm.drainEvents(); Assert.assertEquals("No of assignments must be 0", 0, assigned.size()); assertBlacklistAdditionsAndRemovals(2, 0, rm); // mark h1/h2 as bad nodes nodeManager1.nodeHeartbeat(false); nodeManager2.nodeHeartbeat(false); - dispatcher.await(); + rm.drainEvents(); assigned = allocator.schedule(); - dispatcher.await(); + rm.drainEvents(); assertBlacklistAdditionsAndRemovals(0, 0, rm); Assert.assertEquals("No of assignments must be 0", 0, assigned.size()); nodeManager3.nodeHeartbeat(true); // Node heartbeat - dispatcher.await(); + rm.drainEvents(); assigned = allocator.schedule(); - dispatcher.await(); + rm.drainEvents(); assertBlacklistAdditionsAndRemovals(0, 0, rm); Assert.assertTrue("No of assignments must be 3", assigned.size() == 3); @@ -1352,24 +1325,22 @@ public class TestRMContainerAllocator { MyResourceManager rm = new MyResourceManager(conf); rm.start(); - DrainDispatcher dispatcher = - (DrainDispatcher) rm.getRMContext().getDispatcher(); // Submit the application RMApp app = rm.submitApp(1024); - dispatcher.await(); + rm.drainEvents(); MockNM[] nodeManagers = new MockNM[10]; int nmNum = 0; List assigned = null; - nodeManagers[nmNum] = registerNodeManager(nmNum++, rm, dispatcher); + nodeManagers[nmNum] = registerNodeManager(nmNum++, rm); nodeManagers[0].nodeHeartbeat(true); - dispatcher.await(); + rm.drainEvents(); ApplicationAttemptId appAttemptId = app.getCurrentAppAttempt().getAppAttemptId(); rm.sendAMLaunched(appAttemptId); - dispatcher.await(); + rm.drainEvents(); JobId jobId = MRBuilderUtils.newJobId(appAttemptId.getApplicationId(), 0); Job mockJob = mock(Job.class); @@ -1382,7 +1353,7 @@ public class TestRMContainerAllocator { // Known=1, blacklisted=0, ignore should be false - assign first container assigned = getContainerOnHost(jobId, 1, 1024, new String[] { "h1" }, - nodeManagers[0], dispatcher, allocator, 0, 0, 0, 0, rm); + nodeManagers[0], allocator, 0, 0, 0, 0, rm); Assert.assertEquals("No of assignments must be 1", 1, assigned.size()); LOG.info("Failing container _1 on H1 (Node should be blacklisted and" @@ -1397,47 +1368,47 @@ public class TestRMContainerAllocator { // The current call will send blacklisted node "h1" to RM assigned = getContainerOnHost(jobId, 2, 1024, new String[] { "h1" }, - nodeManagers[0], dispatcher, allocator, 1, 0, 0, 1, rm); + nodeManagers[0], allocator, 1, 0, 0, 1, rm); Assert.assertEquals("No of assignments must be 0", 0, assigned.size()); // Known=1, blacklisted=1, ignore should be true - assign 1 assigned = getContainerOnHost(jobId, 2, 1024, new String[] { "h1" }, - nodeManagers[0], dispatcher, allocator, 0, 0, 0, 0, rm); + nodeManagers[0], allocator, 0, 0, 0, 0, rm); Assert.assertEquals("No of assignments must be 1", 1, assigned.size()); - nodeManagers[nmNum] = registerNodeManager(nmNum++, rm, dispatcher); + nodeManagers[nmNum] = registerNodeManager(nmNum++, rm); // Known=2, blacklisted=1, ignore should be true - assign 1 anyway. assigned = getContainerOnHost(jobId, 3, 1024, new String[] { "h2" }, - nodeManagers[1], dispatcher, allocator, 0, 0, 0, 0, rm); + nodeManagers[1], allocator, 0, 0, 0, 0, rm); Assert.assertEquals("No of assignments must be 1", 1, assigned.size()); - nodeManagers[nmNum] = registerNodeManager(nmNum++, rm, dispatcher); + nodeManagers[nmNum] = registerNodeManager(nmNum++, rm); // Known=3, blacklisted=1, ignore should be true - assign 1 anyway. assigned = getContainerOnHost(jobId, 4, 1024, new String[] { "h3" }, - nodeManagers[2], dispatcher, allocator, 0, 0, 0, 0, rm); + nodeManagers[2], allocator, 0, 0, 0, 0, rm); Assert.assertEquals("No of assignments must be 1", 1, assigned.size()); // Known=3, blacklisted=1, ignore should be true - assign 1 assigned = getContainerOnHost(jobId, 5, 1024, new String[] { "h1" }, - nodeManagers[0], dispatcher, allocator, 0, 0, 0, 0, rm); + nodeManagers[0], allocator, 0, 0, 0, 0, rm); Assert.assertEquals("No of assignments must be 1", 1, assigned.size()); - nodeManagers[nmNum] = registerNodeManager(nmNum++, rm, dispatcher); + nodeManagers[nmNum] = registerNodeManager(nmNum++, rm); // Known=4, blacklisted=1, ignore should be false - assign 1 anyway assigned = getContainerOnHost(jobId, 6, 1024, new String[] { "h4" }, - nodeManagers[3], dispatcher, allocator, 0, 0, 1, 0, rm); + nodeManagers[3], allocator, 0, 0, 1, 0, rm); Assert.assertEquals("No of assignments must be 1", 1, assigned.size()); // Test blacklisting re-enabled. // Known=4, blacklisted=1, ignore should be false - no assignment on h1 assigned = getContainerOnHost(jobId, 7, 1024, new String[] { "h1" }, - nodeManagers[0], dispatcher, allocator, 0, 0, 0, 0, rm); + nodeManagers[0], allocator, 0, 0, 0, 0, rm); Assert.assertEquals("No of assignments must be 0", 0, assigned.size()); // RMContainerRequestor would have created a replacement request. @@ -1450,61 +1421,61 @@ public class TestRMContainerAllocator { // container for the same reason above. assigned = getContainerOnHost(jobId, 8, 1024, new String[] { "h1" }, - nodeManagers[0], dispatcher, allocator, 1, 0, 0, 2, rm); + nodeManagers[0], allocator, 1, 0, 0, 2, rm); Assert.assertEquals("No of assignments must be 0", 0, assigned.size()); // Known=4, blacklisted=2, ignore should be true. Should assign 2 // containers. assigned = getContainerOnHost(jobId, 8, 1024, new String[] { "h1" }, - nodeManagers[0], dispatcher, allocator, 0, 0, 0, 0, rm); + nodeManagers[0], allocator, 0, 0, 0, 0, rm); Assert.assertEquals("No of assignments must be 2", 2, assigned.size()); // Known=4, blacklisted=2, ignore should be true. assigned = getContainerOnHost(jobId, 9, 1024, new String[] { "h2" }, - nodeManagers[1], dispatcher, allocator, 0, 0, 0, 0, rm); + nodeManagers[1], allocator, 0, 0, 0, 0, rm); Assert.assertEquals("No of assignments must be 1", 1, assigned.size()); // Test blacklist while ignore blacklisting enabled ContainerFailedEvent f3 = createFailEvent(jobId, 4, "h3", false); allocator.sendFailure(f3); - nodeManagers[nmNum] = registerNodeManager(nmNum++, rm, dispatcher); + nodeManagers[nmNum] = registerNodeManager(nmNum++, rm); // Known=5, blacklisted=3, ignore should be true. assigned = getContainerOnHost(jobId, 10, 1024, new String[] { "h3" }, - nodeManagers[2], dispatcher, allocator, 0, 0, 0, 0, rm); + nodeManagers[2], allocator, 0, 0, 0, 0, rm); Assert.assertEquals("No of assignments must be 1", 1, assigned.size()); // Assign on 5 more nodes - to re-enable blacklisting for (int i = 0; i < 5; i++) { - nodeManagers[nmNum] = registerNodeManager(nmNum++, rm, dispatcher); + nodeManagers[nmNum] = registerNodeManager(nmNum++, rm); assigned = getContainerOnHost(jobId, 11 + i, 1024, new String[] { String.valueOf(5 + i) }, nodeManagers[4 + i], - dispatcher, allocator, 0, 0, (i == 4 ? 3 : 0), 0, rm); + allocator, 0, 0, (i == 4 ? 3 : 0), 0, rm); Assert.assertEquals("No of assignments must be 1", 1, assigned.size()); } // Test h3 (blacklisted while ignoring blacklisting) is blacklisted. assigned = getContainerOnHost(jobId, 20, 1024, new String[] { "h3" }, - nodeManagers[2], dispatcher, allocator, 0, 0, 0, 0, rm); + nodeManagers[2], allocator, 0, 0, 0, 0, rm); Assert.assertEquals("No of assignments must be 0", 0, assigned.size()); } - private MockNM registerNodeManager(int i, MyResourceManager rm, - DrainDispatcher dispatcher) throws Exception { + private MockNM registerNodeManager(int i, MyResourceManager rm) + throws Exception { MockNM nm = rm.registerNode("h" + (i + 1) + ":1234", 10240); - dispatcher.await(); + rm.drainEvents(); return nm; } private List getContainerOnHost(JobId jobId, int taskAttemptId, int memory, String[] hosts, MockNM mockNM, - DrainDispatcher dispatcher, MyContainerAllocator allocator, + MyContainerAllocator allocator, int expectedAdditions1, int expectedRemovals1, int expectedAdditions2, int expectedRemovals2, MyResourceManager rm) throws Exception { @@ -1514,17 +1485,17 @@ public class TestRMContainerAllocator { // Send the request to the RM List assigned = allocator.schedule(); - dispatcher.await(); + rm.drainEvents(); assertBlacklistAdditionsAndRemovals( expectedAdditions1, expectedRemovals1, rm); Assert.assertEquals("No of assignments must be 0", 0, assigned.size()); // Heartbeat from the required nodeManager mockNM.nodeHeartbeat(true); - dispatcher.await(); + rm.drainEvents(); assigned = allocator.schedule(); - dispatcher.await(); + rm.drainEvents(); assertBlacklistAdditionsAndRemovals( expectedAdditions2, expectedRemovals2, rm); return assigned; @@ -1542,21 +1513,19 @@ public class TestRMContainerAllocator { MyResourceManager rm = new MyResourceManager(conf); rm.start(); - DrainDispatcher dispatcher = (DrainDispatcher) rm.getRMContext() - .getDispatcher(); // Submit the application RMApp app = rm.submitApp(1024); - dispatcher.await(); + rm.drainEvents(); MockNM amNodeManager = rm.registerNode("amNM:1234", 2048); amNodeManager.nodeHeartbeat(true); - dispatcher.await(); + rm.drainEvents(); ApplicationAttemptId appAttemptId = app.getCurrentAppAttempt() .getAppAttemptId(); rm.sendAMLaunched(appAttemptId); - dispatcher.await(); + rm.drainEvents(); JobId jobId = MRBuilderUtils.newJobId(appAttemptId.getApplicationId(), 0); Job mockJob = mock(Job.class); @@ -1569,7 +1538,7 @@ public class TestRMContainerAllocator { // add resources to scheduler MockNM nodeManager1 = rm.registerNode("h1:1234", 10240); MockNM nodeManager3 = rm.registerNode("h3:1234", 10240); - dispatcher.await(); + rm.drainEvents(); LOG.info("Requesting 1 Containers _1 on H1"); // create the container request @@ -1581,17 +1550,17 @@ public class TestRMContainerAllocator { // this tells the scheduler about the requests // as nodes are not added, no allocations List assigned = allocator.schedule(); - dispatcher.await(); + rm.drainEvents(); Assert.assertEquals("No of assignments must be 0", 0, assigned.size()); LOG.info("h1 Heartbeat (To actually schedule the containers)"); // update resources in scheduler nodeManager1.nodeHeartbeat(true); // Node heartbeat - dispatcher.await(); + rm.drainEvents(); LOG.info("RM Heartbeat (To process the scheduled containers)"); assigned = allocator.schedule(); - dispatcher.await(); + rm.drainEvents(); assertBlacklistAdditionsAndRemovals(0, 0, rm); Assert.assertEquals("No of assignments must be 1", 1, assigned.size()); @@ -1608,7 +1577,7 @@ public class TestRMContainerAllocator { //Update the Scheduler with the new requests. assigned = allocator.schedule(); - dispatcher.await(); + rm.drainEvents(); assertBlacklistAdditionsAndRemovals(1, 0, rm); Assert.assertEquals("No of assignments must be 0", 0, assigned.size()); @@ -1623,11 +1592,11 @@ public class TestRMContainerAllocator { LOG.info("h1 Heartbeat (To actually schedule the containers)"); // update resources in scheduler nodeManager1.nodeHeartbeat(true); // Node heartbeat - dispatcher.await(); + rm.drainEvents(); LOG.info("RM Heartbeat (To process the scheduled containers)"); assigned = allocator.schedule(); - dispatcher.await(); + rm.drainEvents(); assertBlacklistAdditionsAndRemovals(0, 0, rm); Assert.assertEquals("No of assignments must be 0", 0, assigned.size()); @@ -1636,19 +1605,19 @@ public class TestRMContainerAllocator { //Send a release for the p:5 container + another request. LOG.info("RM Heartbeat (To process the re-scheduled containers)"); assigned = allocator.schedule(); - dispatcher.await(); + rm.drainEvents(); assertBlacklistAdditionsAndRemovals(0, 0, rm); Assert.assertEquals("No of assignments must be 0", 0, assigned.size()); //Hearbeat from H3 to schedule on this host. LOG.info("h3 Heartbeat (To re-schedule the containers)"); nodeManager3.nodeHeartbeat(true); // Node heartbeat - dispatcher.await(); + rm.drainEvents(); LOG.info("RM Heartbeat (To process the re-scheduled containers for H3)"); assigned = allocator.schedule(); assertBlacklistAdditionsAndRemovals(0, 0, rm); - dispatcher.await(); + rm.drainEvents(); // For debugging for (TaskAttemptContainerAssignedEvent assig : assigned) { @@ -2229,22 +2198,20 @@ public class TestRMContainerAllocator { Configuration conf = new Configuration(); final MyResourceManager rm = new MyResourceManager(conf); rm.start(); - DrainDispatcher dispatcher = (DrainDispatcher) rm.getRMContext() - .getDispatcher(); // Submit the application RMApp app = rm.submitApp(1024); - dispatcher.await(); + rm.drainEvents(); // Make a node to register so as to launch the AM. MockNM amNodeManager = rm.registerNode("amNM:1234", 2048); amNodeManager.nodeHeartbeat(true); - dispatcher.await(); + rm.drainEvents(); ApplicationAttemptId appAttemptId = app.getCurrentAppAttempt() .getAppAttemptId(); rm.sendAMLaunched(appAttemptId); - dispatcher.await(); + rm.drainEvents(); JobId jobId = MRBuilderUtils.newJobId(appAttemptId.getApplicationId(), 0); Job job = mock(Job.class); @@ -2381,21 +2348,19 @@ public class TestRMContainerAllocator { Configuration conf = new Configuration(); final MyResourceManager rm = new MyResourceManager(conf); rm.start(); - DrainDispatcher rmDispatcher = - (DrainDispatcher) rm.getRMContext().getDispatcher(); // Submit the application RMApp rmApp = rm.submitApp(1024); - rmDispatcher.await(); + rm.drainEvents(); MockNM amNodeManager = rm.registerNode("127.0.0.1:1234", 11264); amNodeManager.nodeHeartbeat(true); - rmDispatcher.await(); + rm.drainEvents(); final ApplicationAttemptId appAttemptId = rmApp.getCurrentAppAttempt().getAppAttemptId(); rm.sendAMLaunched(appAttemptId); - rmDispatcher.await(); + rm.drainEvents(); MRApp mrApp = new MRApp(appAttemptId, ContainerId.newContainerId(appAttemptId, 0), 10, @@ -2454,22 +2419,20 @@ public class TestRMContainerAllocator { MyResourceManager rm1 = new MyResourceManager(conf, memStore); rm1.start(); - DrainDispatcher dispatcher = - (DrainDispatcher) rm1.getRMContext().getDispatcher(); // Submit the application RMApp app = rm1.submitApp(1024); - dispatcher.await(); + rm1.drainEvents(); MockNM nm1 = new MockNM("h1:1234", 15120, rm1.getResourceTrackerService()); nm1.registerNode(); nm1.nodeHeartbeat(true); // Node heartbeat - dispatcher.await(); + rm1.drainEvents(); ApplicationAttemptId appAttemptId = app.getCurrentAppAttempt().getAppAttemptId(); rm1.sendAMLaunched(appAttemptId); - dispatcher.await(); + rm1.drainEvents(); JobId jobId = MRBuilderUtils.newJobId(appAttemptId.getApplicationId(), 0); Job mockJob = mock(Job.class); @@ -2498,7 +2461,7 @@ public class TestRMContainerAllocator { // send allocate request and 1 blacklisted nodes List assignedContainers = allocator.schedule(); - dispatcher.await(); + rm1.drainEvents(); Assert.assertEquals("No of assignments must be 0", 0, assignedContainers.size()); // Why ask is 3, not 4? --> ask from blacklisted node h2 is removed @@ -2506,11 +2469,11 @@ public class TestRMContainerAllocator { assertBlacklistAdditionsAndRemovals(1, 0, rm1); nm1.nodeHeartbeat(true); // Node heartbeat - dispatcher.await(); + rm1.drainEvents(); // Step-2 : 2 containers are allocated by RM. assignedContainers = allocator.schedule(); - dispatcher.await(); + rm1.drainEvents(); Assert.assertEquals("No of assignments must be 2", 2, assignedContainers.size()); assertAsksAndReleases(0, 0, rm1); @@ -2545,7 +2508,6 @@ public class TestRMContainerAllocator { rm2.start(); nm1.setResourceTrackerService(rm2.getResourceTrackerService()); allocator.updateSchedulerProxy(rm2); - dispatcher = (DrainDispatcher) rm2.getRMContext().getDispatcher(); // NM should be rebooted on heartbeat, even first heartbeat for nm2 NodeHeartbeatResponse hbResponse = nm1.nodeHeartbeat(true); @@ -2555,7 +2517,7 @@ public class TestRMContainerAllocator { nm1 = new MockNM("h1:1234", 10240, rm2.getResourceTrackerService()); nm1.registerNode(); nm1.nodeHeartbeat(true); - dispatcher.await(); + rm2.drainEvents(); // Step-4 : On RM restart, AM(does not know RM is restarted) sends // additional containerRequest(event4) and blacklisted nodes. @@ -2576,7 +2538,7 @@ public class TestRMContainerAllocator { // send allocate request to 2nd RM and get resync command allocator.schedule(); - dispatcher.await(); + rm2.drainEvents(); // Step-5 : On Resync,AM sends all outstanding // asks,release,blacklistAaddition @@ -2587,16 +2549,16 @@ public class TestRMContainerAllocator { // send all outstanding request again. assignedContainers = allocator.schedule(); - dispatcher.await(); + rm2.drainEvents(); assertAsksAndReleases(3, 2, rm2); assertBlacklistAdditionsAndRemovals(2, 0, rm2); nm1.nodeHeartbeat(true); - dispatcher.await(); + rm2.drainEvents(); // Step-6 : RM allocates containers i.e event3,event4 and cRequest5 assignedContainers = allocator.schedule(); - dispatcher.await(); + rm2.drainEvents(); Assert.assertEquals("Number of container should be 3", 3, assignedContainers.size()); @@ -2699,20 +2661,19 @@ public class TestRMContainerAllocator { MRJobConfig.MR_AM_TO_RM_WAIT_INTERVAL_MS, 0); MyResourceManager rm1 = new MyResourceManager(conf); rm1.start(); - DrainDispatcher dispatcher = - (DrainDispatcher) rm1.getRMContext().getDispatcher(); + RMApp app = rm1.submitApp(1024); - dispatcher.await(); + rm1.drainEvents(); MockNM nm1 = new MockNM("h1:1234", 15120, rm1.getResourceTrackerService()); nm1.registerNode(); nm1.nodeHeartbeat(true); - dispatcher.await(); + rm1.drainEvents(); ApplicationAttemptId appAttemptId = app.getCurrentAppAttempt().getAppAttemptId(); rm1.sendAMLaunched(appAttemptId); - dispatcher.await(); + rm1.drainEvents(); JobId jobId = MRBuilderUtils.newJobId(appAttemptId.getApplicationId(), 0); Job mockJob = mock(Job.class); @@ -2728,7 +2689,7 @@ public class TestRMContainerAllocator { } catch (RMContainerAllocationException e) { Assert.assertTrue(e.getMessage().contains("Could not contact RM after")); } - dispatcher.await(); + rm1.drainEvents(); Assert.assertEquals("Should Have 1 Job Event", 1, allocator.jobEvents.size()); JobEvent event = allocator.jobEvents.get(0); @@ -2750,22 +2711,20 @@ public class TestRMContainerAllocator { rm.start(); AMRMTokenSecretManager secretMgr = rm.getRMContext().getAMRMTokenSecretManager(); - DrainDispatcher dispatcher = (DrainDispatcher) rm.getRMContext() - .getDispatcher(); // Submit the application RMApp app = rm.submitApp(1024); - dispatcher.await(); + rm.drainEvents(); MockNM amNodeManager = rm.registerNode("amNM:1234", 2048); amNodeManager.nodeHeartbeat(true); - dispatcher.await(); + rm.drainEvents(); final ApplicationAttemptId appAttemptId = app.getCurrentAppAttempt() .getAppAttemptId(); final ApplicationId appId = app.getApplicationId(); rm.sendAMLaunched(appAttemptId); - dispatcher.await(); + rm.drainEvents(); JobId jobId = MRBuilderUtils.newJobId(appAttemptId.getApplicationId(), 0); final Job mockJob = mock(Job.class); @@ -2953,21 +2912,19 @@ public class TestRMContainerAllocator { Configuration conf = new Configuration(); MyResourceManager rm = new MyResourceManager(conf); rm.start(); - DrainDispatcher dispatcher = (DrainDispatcher) rm.getRMContext() - .getDispatcher(); // Submit the application RMApp app = rm.submitApp(1024); - dispatcher.await(); + rm.drainEvents(); MockNM amNodeManager = rm.registerNode("amNM:1234", 2048); amNodeManager.nodeHeartbeat(true); - dispatcher.await(); + rm.drainEvents(); ApplicationAttemptId appAttemptId = app.getCurrentAppAttempt() .getAppAttemptId(); rm.sendAMLaunched(appAttemptId); - dispatcher.await(); + rm.drainEvents(); JobId jobId = MRBuilderUtils.newJobId(appAttemptId.getApplicationId(), 0); Job mockJob = mock(Job.class); @@ -2989,21 +2946,19 @@ public class TestRMContainerAllocator { Configuration conf = new Configuration(); MyResourceManager rm = new MyResourceManager(conf); rm.start(); - DrainDispatcher dispatcher = - (DrainDispatcher) rm.getRMContext().getDispatcher(); // Submit the application RMApp app = rm.submitApp(1024); - dispatcher.await(); + rm.drainEvents(); MockNM amNodeManager = rm.registerNode("amNM:1234", 1260); amNodeManager.nodeHeartbeat(true); - dispatcher.await(); + rm.drainEvents(); ApplicationAttemptId appAttemptId = app.getCurrentAppAttempt() .getAppAttemptId(); rm.sendAMLaunched(appAttemptId); - dispatcher.await(); + rm.drainEvents(); JobId jobId = MRBuilderUtils.newJobId(appAttemptId.getApplicationId(), 0); Job mockJob = mock(Job.class); @@ -3018,7 +2973,7 @@ public class TestRMContainerAllocator { // Register nodes to RM. MockNM nodeManager = rm.registerNode("h1:1234", 1024); - dispatcher.await(); + rm.drainEvents(); // Request 2 maps and 1 reducer(sone on nodes which are not registered). ContainerRequestEvent event1 = @@ -3034,7 +2989,7 @@ public class TestRMContainerAllocator { // This will tell the scheduler about the requests but there will be no // allocations as nodes are not added. allocator.schedule(); - dispatcher.await(); + rm.drainEvents(); // Advance clock so that maps can be considered as hanging. clock.setTime(System.currentTimeMillis() + 500000L); @@ -3045,15 +3000,15 @@ public class TestRMContainerAllocator { allocator.sendRequest(event4); allocator.schedule(); - dispatcher.await(); + rm.drainEvents(); // Update resources in scheduler through node heartbeat from h1. nodeManager.nodeHeartbeat(true); - dispatcher.await(); + rm.drainEvents(); rm.getMyFifoScheduler().forceResourceLimit(Resource.newInstance(1024, 1)); allocator.schedule(); - dispatcher.await(); + rm.drainEvents(); // One map is assigned. Assert.assertEquals(1, allocator.getAssignedRequests().maps.size()); @@ -3087,7 +3042,7 @@ public class TestRMContainerAllocator { // On next allocate request to scheduler, headroom reported will be 0. rm.getMyFifoScheduler().forceResourceLimit(Resource.newInstance(0, 0)); allocator.schedule(); - dispatcher.await(); + rm.drainEvents(); // After allocate response from scheduler, all scheduled reduces are ramped // down and move to pending. 3 asks are also updated with 0 containers to // indicate ramping down of reduces to scheduler. @@ -3155,21 +3110,19 @@ public class TestRMContainerAllocator { Configuration conf = new Configuration(); MyResourceManager rm = new MyResourceManager(conf); rm.start(); - DrainDispatcher dispatcher = - (DrainDispatcher) rm.getRMContext().getDispatcher(); // Submit the application RMApp app = rm.submitApp(1024); - dispatcher.await(); + rm.drainEvents(); MockNM amNodeManager = rm.registerNode("amNM:1234", 1260); amNodeManager.nodeHeartbeat(true); - dispatcher.await(); + rm.drainEvents(); ApplicationAttemptId appAttemptId = app.getCurrentAppAttempt() .getAppAttemptId(); rm.sendAMLaunched(appAttemptId); - dispatcher.await(); + rm.drainEvents(); JobId jobId = MRBuilderUtils.newJobId(appAttemptId.getApplicationId(), 0); Job mockJob = mock(Job.class); @@ -3184,7 +3137,7 @@ public class TestRMContainerAllocator { // Register nodes to RM. MockNM nodeManager = rm.registerNode("h1:1234", 1024); - dispatcher.await(); + rm.drainEvents(); // Request 2 maps and 1 reducer(sone on nodes which are not registered). ContainerRequestEvent event1 = @@ -3200,7 +3153,7 @@ public class TestRMContainerAllocator { // This will tell the scheduler about the requests but there will be no // allocations as nodes are not added. allocator.schedule(); - dispatcher.await(); + rm.drainEvents(); // Advance clock so that maps can be considered as hanging. clock.setTime(System.currentTimeMillis() + 500000L); @@ -3211,15 +3164,15 @@ public class TestRMContainerAllocator { allocator.sendRequest(event4); allocator.schedule(); - dispatcher.await(); + rm.drainEvents(); // Update resources in scheduler through node heartbeat from h1. nodeManager.nodeHeartbeat(true); - dispatcher.await(); + rm.drainEvents(); rm.getMyFifoScheduler().forceResourceLimit(Resource.newInstance(1024, 1)); allocator.schedule(); - dispatcher.await(); + rm.drainEvents(); // One map is assigned. Assert.assertEquals(1, allocator.getAssignedRequests().maps.size()); @@ -3256,7 +3209,7 @@ public class TestRMContainerAllocator { // On next allocate request to scheduler, headroom reported will be 2048. rm.getMyFifoScheduler().forceResourceLimit(Resource.newInstance(2048, 0)); allocator.schedule(); - dispatcher.await(); + rm.drainEvents(); // After allocate response from scheduler, all scheduled reduces are ramped // down and move to pending. 3 asks are also updated with 0 containers to // indicate ramping down of reduces to scheduler. @@ -3285,21 +3238,19 @@ public class TestRMContainerAllocator { conf.setInt(MRJobConfig.MR_JOB_REDUCER_UNCONDITIONAL_PREEMPT_DELAY_SEC, -1); MyResourceManager rm = new MyResourceManager(conf); rm.start(); - DrainDispatcher dispatcher = - (DrainDispatcher) rm.getRMContext().getDispatcher(); // Submit the application RMApp app = rm.submitApp(1024); - dispatcher.await(); + rm.drainEvents(); MockNM amNodeManager = rm.registerNode("amNM:1234", 1260); amNodeManager.nodeHeartbeat(true); - dispatcher.await(); + rm.drainEvents(); ApplicationAttemptId appAttemptId = app.getCurrentAppAttempt() .getAppAttemptId(); rm.sendAMLaunched(appAttemptId); - dispatcher.await(); + rm.drainEvents(); JobId jobId = MRBuilderUtils.newJobId(appAttemptId.getApplicationId(), 0); Job mockJob = mock(Job.class); @@ -3315,10 +3266,10 @@ public class TestRMContainerAllocator { appAttemptId, mockJob); MockNM nodeManager = rm.registerNode("h1:1234", 4096); - dispatcher.await(); + rm.drainEvents(); // Register nodes to RM. MockNM nodeManager2 = rm.registerNode("h2:1234", 1024); - dispatcher.await(); + rm.drainEvents(); // Request 2 maps and 1 reducer(sone on nodes which are not registered). ContainerRequestEvent event1 = @@ -3334,7 +3285,7 @@ public class TestRMContainerAllocator { // This will tell the scheduler about the requests but there will be no // allocations as nodes are not added. allocator.schedule(); - dispatcher.await(); + rm.drainEvents(); // Request for another reducer on h3 which has not registered. ContainerRequestEvent event4 = @@ -3342,15 +3293,15 @@ public class TestRMContainerAllocator { allocator.sendRequest(event4); allocator.schedule(); - dispatcher.await(); + rm.drainEvents(); // Update resources in scheduler through node heartbeat from h1. nodeManager.nodeHeartbeat(true); - dispatcher.await(); + rm.drainEvents(); rm.getMyFifoScheduler().forceResourceLimit(Resource.newInstance(3072, 3)); allocator.schedule(); - dispatcher.await(); + rm.drainEvents(); // Two maps are assigned. Assert.assertEquals(2, allocator.getAssignedRequests().maps.size()); @@ -3363,15 +3314,15 @@ public class TestRMContainerAllocator { Assert.assertEquals(0, allocator.getAssignedRequests().maps.size()); nodeManager.nodeHeartbeat(true); - dispatcher.await(); + rm.drainEvents(); rm.getMyFifoScheduler().forceResourceLimit(Resource.newInstance(1024, 1)); allocator.schedule(); - dispatcher.await(); + rm.drainEvents(); // h2 heartbeats. nodeManager2.nodeHeartbeat(true); - dispatcher.await(); + rm.drainEvents(); // Send request for one more mapper. ContainerRequestEvent event5 = @@ -3380,7 +3331,7 @@ public class TestRMContainerAllocator { rm.getMyFifoScheduler().forceResourceLimit(Resource.newInstance(2048, 2)); allocator.schedule(); - dispatcher.await(); + rm.drainEvents(); // One reducer is assigned and one map is scheduled Assert.assertEquals(1, allocator.getScheduledRequests().maps.size()); Assert.assertEquals(1, allocator.getAssignedRequests().reduces.size()); @@ -3388,7 +3339,7 @@ public class TestRMContainerAllocator { // enough if scheduled reducers resources are deducted. rm.getMyFifoScheduler().forceResourceLimit(Resource.newInstance(1260, 2)); allocator.schedule(); - dispatcher.await(); + rm.drainEvents(); // After allocate response, the one assigned reducer is preempted and killed Assert.assertEquals(1, MyContainerAllocator.getTaskAttemptKillEvents().size()); Assert.assertEquals(RMContainerAllocator.RAMPDOWN_DIAGNOSTIC, diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-client/src/test/java/org/apache/hadoop/yarn/client/api/impl/TestAMRMClientOnRMRestart.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-client/src/test/java/org/apache/hadoop/yarn/client/api/impl/TestAMRMClientOnRMRestart.java index 39a76333ab8..fa3c6afeb36 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-client/src/test/java/org/apache/hadoop/yarn/client/api/impl/TestAMRMClientOnRMRestart.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-client/src/test/java/org/apache/hadoop/yarn/client/api/impl/TestAMRMClientOnRMRestart.java @@ -49,8 +49,6 @@ import org.apache.hadoop.yarn.api.records.UpdateContainerRequest; import org.apache.hadoop.yarn.client.api.AMRMClient; import org.apache.hadoop.yarn.client.api.AMRMClient.ContainerRequest; import org.apache.hadoop.yarn.conf.YarnConfiguration; -import org.apache.hadoop.yarn.event.Dispatcher; -import org.apache.hadoop.yarn.event.DrainDispatcher; import org.apache.hadoop.yarn.event.EventHandler; import org.apache.hadoop.yarn.ipc.YarnRPC; import org.apache.hadoop.yarn.security.AMRMTokenIdentifier; @@ -126,22 +124,20 @@ public class TestAMRMClientOnRMRestart { // Phase-1 Start 1st RM MyResourceManager rm1 = new MyResourceManager(conf, memStore); rm1.start(); - DrainDispatcher dispatcher = - (DrainDispatcher) rm1.getRMContext().getDispatcher(); // Submit the application RMApp app = rm1.submitApp(1024); - dispatcher.await(); + rm1.drainEvents(); MockNM nm1 = new MockNM("h1:1234", 15120, rm1.getResourceTrackerService()); nm1.registerNode(); nm1.nodeHeartbeat(true); // Node heartbeat - dispatcher.await(); + rm1.drainEvents(); ApplicationAttemptId appAttemptId = app.getCurrentAppAttempt().getAppAttemptId(); rm1.sendAMLaunched(appAttemptId); - dispatcher.await(); + rm1.drainEvents(); org.apache.hadoop.security.token.Token token = rm1.getRMContext().getRMApps().get(appAttemptId.getApplicationId()) @@ -176,7 +172,7 @@ public class TestAMRMClientOnRMRestart { blacklistAdditions.remove("h2");// remove from local list AllocateResponse allocateResponse = amClient.allocate(0.1f); - dispatcher.await(); + rm1.drainEvents(); Assert.assertEquals("No of assignments must be 0", 0, allocateResponse .getAllocatedContainers().size()); @@ -189,10 +185,10 @@ public class TestAMRMClientOnRMRestart { // Step-2 : NM heart beat is sent. // On 2nd AM allocate request, RM allocates 3 containers to AM nm1.nodeHeartbeat(true); // Node heartbeat - dispatcher.await(); + rm1.drainEvents(); allocateResponse = amClient.allocate(0.2f); - dispatcher.await(); + rm1.drainEvents(); // 3 containers are allocated i.e for cRequest1, cRequest2 and cRequest3. Assert.assertEquals("No of assignments must be 0", 3, allocateResponse .getAllocatedContainers().size()); @@ -207,7 +203,7 @@ public class TestAMRMClientOnRMRestart { amClient.removeContainerRequest(cRequest3); allocateResponse = amClient.allocate(0.2f); - dispatcher.await(); + rm1.drainEvents(); Assert.assertEquals("No of assignments must be 0", 0, allocateResponse .getAllocatedContainers().size()); assertAsksAndReleases(4, 0, rm1); @@ -233,7 +229,7 @@ public class TestAMRMClientOnRMRestart { // request nm1.nodeHeartbeat(containerId.getApplicationAttemptId(), containerId.getContainerId(), ContainerState.RUNNING); - dispatcher.await(); + rm1.drainEvents(); amClient.requestContainerUpdate( container, UpdateContainerRequest.newInstance( container.getVersion(), container.getId(), @@ -242,7 +238,7 @@ public class TestAMRMClientOnRMRestart { it.remove(); allocateResponse = amClient.allocate(0.3f); - dispatcher.await(); + rm1.drainEvents(); Assert.assertEquals("No of assignments must be 0", 0, allocateResponse .getAllocatedContainers().size()); assertAsksAndReleases(3, pendingRelease, rm1); @@ -258,7 +254,6 @@ public class TestAMRMClientOnRMRestart { rm2.start(); nm1.setResourceTrackerService(rm2.getResourceTrackerService()); ((MyAMRMClientImpl) amClient).updateRMProxy(rm2); - dispatcher = (DrainDispatcher) rm2.getRMContext().getDispatcher(); // NM should be rebooted on heartbeat, even first heartbeat for nm2 NodeHeartbeatResponse hbResponse = nm1.nodeHeartbeat(true); @@ -274,7 +269,7 @@ public class TestAMRMClientOnRMRestart { Collections.singletonList( containerId.getApplicationAttemptId().getApplicationId())); nm1.nodeHeartbeat(true); - dispatcher.await(); + rm2.drainEvents(); blacklistAdditions.add("h3"); amClient.updateBlacklist(blacklistAdditions, null); @@ -296,7 +291,7 @@ public class TestAMRMClientOnRMRestart { // containerRequest and blacklisted nodes. // Intern RM send resync command,AMRMClient resend allocate request allocateResponse = amClient.allocate(0.3f); - dispatcher.await(); + rm2.drainEvents(); completedContainer = allocateResponse.getCompletedContainersStatuses().size(); @@ -313,7 +308,7 @@ public class TestAMRMClientOnRMRestart { // Step-5 : Allocater after resync command allocateResponse = amClient.allocate(0.5f); - dispatcher.await(); + rm2.drainEvents(); Assert.assertEquals("No of assignments must be 0", 0, allocateResponse .getAllocatedContainers().size()); @@ -326,10 +321,10 @@ public class TestAMRMClientOnRMRestart { int count = 5; while (count-- > 0) { nm1.nodeHeartbeat(true); - dispatcher.await(); + rm2.drainEvents(); allocateResponse = amClient.allocate(0.5f); - dispatcher.await(); + rm2.drainEvents(); noAssignedContainer += allocateResponse.getAllocatedContainers().size(); if (noAssignedContainer == 3) { break; @@ -358,22 +353,20 @@ public class TestAMRMClientOnRMRestart { // Phase-1 Start 1st RM MyResourceManager rm1 = new MyResourceManager(conf, memStore); rm1.start(); - DrainDispatcher dispatcher = - (DrainDispatcher) rm1.getRMContext().getDispatcher(); // Submit the application RMApp app = rm1.submitApp(1024); - dispatcher.await(); + rm1.drainEvents(); MockNM nm1 = new MockNM("h1:1234", 15120, rm1.getResourceTrackerService()); nm1.registerNode(); nm1.nodeHeartbeat(true); // Node heartbeat - dispatcher.await(); + rm1.drainEvents(); ApplicationAttemptId appAttemptId = app.getCurrentAppAttempt().getAppAttemptId(); rm1.sendAMLaunched(appAttemptId); - dispatcher.await(); + rm1.drainEvents(); org.apache.hadoop.security.token.Token token = rm1.getRMContext().getRMApps().get(appAttemptId.getApplicationId()) @@ -393,7 +386,6 @@ public class TestAMRMClientOnRMRestart { rm2.start(); nm1.setResourceTrackerService(rm2.getResourceTrackerService()); ((MyAMRMClientImpl) amClient).updateRMProxy(rm2); - dispatcher = (DrainDispatcher) rm2.getRMContext().getDispatcher(); // NM should be rebooted on heartbeat, even first heartbeat for nm2 NodeHeartbeatResponse hbResponse = nm1.nodeHeartbeat(true); @@ -409,7 +401,7 @@ public class TestAMRMClientOnRMRestart { Priority.newInstance(0), 0); nm1.registerNode(Arrays.asList(containerReport), null); nm1.nodeHeartbeat(true); - dispatcher.await(); + rm2.drainEvents(); amClient.unregisterApplicationMaster(FinalApplicationStatus.SUCCEEDED, null, null); @@ -421,7 +413,6 @@ public class TestAMRMClientOnRMRestart { amClient.stop(); rm1.stop(); rm2.stop(); - } @@ -439,22 +430,20 @@ public class TestAMRMClientOnRMRestart { // start first RM MyResourceManager2 rm1 = new MyResourceManager2(conf, memStore); rm1.start(); - DrainDispatcher dispatcher = - (DrainDispatcher) rm1.getRMContext().getDispatcher(); Long startTime = System.currentTimeMillis(); // Submit the application RMApp app = rm1.submitApp(1024); - dispatcher.await(); + rm1.drainEvents(); MockNM nm1 = new MockNM("h1:1234", 15120, rm1.getResourceTrackerService()); nm1.registerNode(); nm1.nodeHeartbeat(true); // Node heartbeat - dispatcher.await(); + rm1.drainEvents(); ApplicationAttemptId appAttemptId = app.getCurrentAppAttempt().getAppAttemptId(); rm1.sendAMLaunched(appAttemptId); - dispatcher.await(); + rm1.drainEvents(); AMRMTokenSecretManager amrmTokenSecretManagerForRM1 = rm1.getRMContext().getAMRMTokenSecretManager(); @@ -513,7 +502,6 @@ public class TestAMRMClientOnRMRestart { rm2.start(); nm1.setResourceTrackerService(rm2.getResourceTrackerService()); ((MyAMRMClientImpl) amClient).updateRMProxy(rm2); - dispatcher = (DrainDispatcher) rm2.getRMContext().getDispatcher(); AMRMTokenSecretManager amrmTokenSecretManagerForRM2 = rm2.getRMContext().getAMRMTokenSecretManager(); @@ -615,11 +603,6 @@ public class TestAMRMClientOnRMRestart { MyResourceManager.setClusterTimeStamp(fakeClusterTimeStamp); } - @Override - protected Dispatcher createDispatcher() { - return new DrainDispatcher(); - } - @Override protected EventHandler createSchedulerEventDispatcher() { // Dispatch inline for test sanity diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/ACLsTestBase.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/ACLsTestBase.java index fbd5ac38dc0..100eb7f21a9 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/ACLsTestBase.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/ACLsTestBase.java @@ -30,14 +30,9 @@ import org.apache.hadoop.security.authorize.AccessControlList; import org.apache.hadoop.service.Service.STATE; import org.apache.hadoop.yarn.api.ApplicationClientProtocol; import org.apache.hadoop.yarn.conf.YarnConfiguration; -import org.apache.hadoop.yarn.event.Dispatcher; -import org.apache.hadoop.yarn.event.DrainDispatcher; import org.apache.hadoop.yarn.ipc.YarnRPC; import org.junit.Before; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - public abstract class ACLsTestBase { protected static final String COMMON_USER = "common_user"; @@ -80,11 +75,6 @@ public abstract class ACLsTestBase { .getRMDelegationTokenSecretManager()); } - @Override - protected Dispatcher createDispatcher() { - return new DrainDispatcher(); - } - @Override protected void doSecureLogin() throws IOException { } diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/RMHATestBase.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/RMHATestBase.java index c9ce7d7a061..c95bcdfca97 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/RMHATestBase.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/RMHATestBase.java @@ -26,22 +26,17 @@ import org.apache.hadoop.ha.HAServiceProtocol; import org.apache.hadoop.ha.HAServiceProtocol.HAServiceState; import org.apache.hadoop.ha.HAServiceProtocol.StateChangeRequestInfo; import org.apache.hadoop.yarn.api.records.ApplicationSubmissionContext; -import org.apache.hadoop.yarn.api.records.ResourceRequest; import org.apache.hadoop.yarn.conf.HAUtil; import org.apache.hadoop.yarn.conf.YarnConfiguration; -import org.apache.hadoop.yarn.event.Dispatcher; -import org.apache.hadoop.yarn.event.DrainDispatcher; import org.apache.hadoop.yarn.exceptions.YarnException; import org.apache.hadoop.yarn.server.resourcemanager.recovery.ZKRMStateStore; import org.apache.hadoop.yarn.server.resourcemanager.rmapp.RMApp; import org.apache.hadoop.yarn.server.resourcemanager.rmapp.RMAppImpl; import org.apache.hadoop.yarn.server.resourcemanager.rmapp.RMAppState; import org.apache.hadoop.yarn.server.resourcemanager.rmapp.attempt.RMAppAttempt; -import org.apache.hadoop.yarn.server.resourcemanager.rmapp.attempt.RMAppAttemptImpl; import org.apache.hadoop.yarn.server.resourcemanager.rmapp.attempt.RMAppAttemptState; import org.apache.hadoop.yarn.server.resourcemanager.scheduler.YarnScheduler; import org.apache.hadoop.yarn.server.security.ApplicationACLsManager; -import org.apache.hadoop.yarn.server.utils.BuilderUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -108,20 +103,9 @@ public abstract class RMHATestBase extends ClientBaseWithFixes{ } protected void startRMs() throws IOException { - rm1 = new MockRM(confForRM1, null, false, false){ - @Override - protected Dispatcher createDispatcher() { - return new DrainDispatcher(); - } - }; - rm2 = new MockRM(confForRM2, null, false, false){ - @Override - protected Dispatcher createDispatcher() { - return new DrainDispatcher(); - } - }; + rm1 = new MockRM(confForRM1, null, false, false); + rm2 = new MockRM(confForRM2, null, false, false); startRMs(rm1, confForRM1, rm2, confForRM2); - } protected void startRMsWithCustomizedRMAppManager() throws IOException { diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/ReservationACLsTestBase.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/ReservationACLsTestBase.java index 03bc8897ab4..c8ee00e60bc 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/ReservationACLsTestBase.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/ReservationACLsTestBase.java @@ -47,7 +47,6 @@ import org.apache.hadoop.yarn.api.records.ReservationRequest; import org.apache.hadoop.yarn.api.records.ReservationRequestInterpreter; import org.apache.hadoop.yarn.api.records.ReservationRequests; import org.apache.hadoop.yarn.conf.YarnConfiguration; -import org.apache.hadoop.yarn.event.DrainDispatcher; import org.apache.hadoop.yarn.exceptions.YarnException; import org.apache.hadoop.yarn.server.resourcemanager.reservation.Plan; import org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CapacityScheduler; @@ -463,9 +462,7 @@ public class ReservationACLsTestBase extends ACLsTestBase { int attempts = 10; Collection plans; do { - DrainDispatcher dispatcher = - (DrainDispatcher) resourceManager.getRMContext().getDispatcher(); - dispatcher.await(); + resourceManager.drainEvents(); LOG.info("Waiting for node capacity to be added to plan"); plans = resourceManager.getRMContext().getReservationSystem() .getAllPlans().values(); diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestApplicationCleanup.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestApplicationCleanup.java index c4197a1c1f8..422b7eb88a2 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestApplicationCleanup.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestApplicationCleanup.java @@ -40,8 +40,6 @@ import org.apache.hadoop.yarn.api.records.Priority; import org.apache.hadoop.yarn.api.records.Resource; import org.apache.hadoop.yarn.api.records.ResourceRequest; import org.apache.hadoop.yarn.conf.YarnConfiguration; -import org.apache.hadoop.yarn.event.Dispatcher; -import org.apache.hadoop.yarn.event.DrainDispatcher; import org.apache.hadoop.yarn.server.api.protocolrecords.NMContainerStatus; import org.apache.hadoop.yarn.server.api.protocolrecords.NodeHeartbeatResponse; import org.apache.hadoop.yarn.server.resourcemanager.recovery.MemoryRMStateStore; @@ -161,13 +159,7 @@ public class TestApplicationCleanup { Logger rootLogger = LogManager.getRootLogger(); rootLogger.setLevel(Level.DEBUG); - final DrainDispatcher dispatcher = new DrainDispatcher(); - MockRM rm = new MockRM() { - @Override - protected Dispatcher createDispatcher() { - return dispatcher; - } - }; + MockRM rm = new MockRM(); rm.start(); MockNM nm1 = rm.registerNode("127.0.0.1:1234", 5000); @@ -185,8 +177,8 @@ public class TestApplicationCleanup { int request = 2; am.allocate("127.0.0.1" , 1000, request, new ArrayList()); - dispatcher.await(); - + rm.drainEvents(); + //kick the scheduler nm1.nodeHeartbeat(true); List conts = am.allocate(new ArrayList(), @@ -199,7 +191,7 @@ public class TestApplicationCleanup { Thread.sleep(100); conts = am.allocate(new ArrayList(), new ArrayList()).getAllocatedContainers(); - dispatcher.await(); + rm.drainEvents(); contReceived += conts.size(); nm1.nodeHeartbeat(true); } @@ -209,7 +201,7 @@ public class TestApplicationCleanup { ArrayList release = new ArrayList(); release.add(conts.get(0).getId()); am.allocate(new ArrayList(), release); - dispatcher.await(); + rm.drainEvents(); // Send one more heartbeat with a fake running container. This is to // simulate the situation that can happen if the NM reports that container @@ -224,7 +216,7 @@ public class TestApplicationCleanup { containerStatuses.put(app.getApplicationId(), containerStatusList); NodeHeartbeatResponse resp = nm1.nodeHeartbeat(containerStatuses, true); - waitForContainerCleanup(dispatcher, nm1, resp); + waitForContainerCleanup(rm, nm1, resp); // Now to test the case when RM already gave cleanup, and NM suddenly // realizes that the container is running. @@ -240,17 +232,17 @@ public class TestApplicationCleanup { resp = nm1.nodeHeartbeat(containerStatuses, true); // The cleanup list won't be instantaneous as it is given out by scheduler // and not RMNodeImpl. - waitForContainerCleanup(dispatcher, nm1, resp); + waitForContainerCleanup(rm, nm1, resp); rm.stop(); } - protected void waitForContainerCleanup(DrainDispatcher dispatcher, MockNM nm, + protected void waitForContainerCleanup(MockRM rm, MockNM nm, NodeHeartbeatResponse resp) throws Exception { int waitCount = 0, cleanedConts = 0; List contsToClean; do { - dispatcher.await(); + rm.drainEvents(); contsToClean = resp.getContainersToCleanup(); cleanedConts += contsToClean.size(); if (cleanedConts >= 1) { @@ -400,13 +392,7 @@ public class TestApplicationCleanup { memStore.init(conf); // start RM - final DrainDispatcher dispatcher = new DrainDispatcher(); - MockRM rm1 = new MockRM(conf, memStore) { - @Override - protected Dispatcher createDispatcher() { - return dispatcher; - } - }; + MockRM rm1 = new MockRM(conf, memStore); rm1.start(); MockNM nm1 = new MockNM("127.0.0.1:1234", 15120, rm1.getResourceTrackerService()); @@ -419,13 +405,7 @@ public class TestApplicationCleanup { rm1.waitForState(app0.getApplicationId(), RMAppState.RUNNING); // start new RM - final DrainDispatcher dispatcher2 = new DrainDispatcher(); - MockRM rm2 = new MockRM(conf, memStore) { - @Override - protected Dispatcher createDispatcher() { - return dispatcher2; - } - }; + MockRM rm2 = new MockRM(conf, memStore); rm2.start(); // nm1 register to rm2, and do a heartbeat @@ -437,7 +417,7 @@ public class TestApplicationCleanup { NodeHeartbeatResponse response = nm1.nodeHeartbeat(am0 .getApplicationAttemptId(), 2, ContainerState.RUNNING); - waitForContainerCleanup(dispatcher2, nm1, response); + waitForContainerCleanup(rm2, nm1, response); rm1.stop(); rm2.stop(); diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestApplicationMasterLauncher.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestApplicationMasterLauncher.java index 08b180fc413..9e8401027e9 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestApplicationMasterLauncher.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestApplicationMasterLauncher.java @@ -59,8 +59,6 @@ import org.apache.hadoop.yarn.api.records.ResourceRequest; import org.apache.hadoop.yarn.api.records.SerializedException; import org.apache.hadoop.yarn.api.records.Token; import org.apache.hadoop.yarn.conf.YarnConfiguration; -import org.apache.hadoop.yarn.event.Dispatcher; -import org.apache.hadoop.yarn.event.DrainDispatcher; import org.apache.hadoop.yarn.exceptions.ApplicationAttemptNotFoundException; import org.apache.hadoop.yarn.exceptions.ApplicationMasterNotRegisteredException; import org.apache.hadoop.yarn.exceptions.NMNotYetReadyException; @@ -260,7 +258,6 @@ public class TestApplicationMasterLauncher { Configuration conf = new Configuration(); conf.setInt(YarnConfiguration.RM_AM_MAX_ATTEMPTS, 1); conf.setInt(YarnConfiguration.CLIENT_NM_CONNECT_RETRY_INTERVAL_MS, 1); - final DrainDispatcher dispatcher = new DrainDispatcher(); MockRM rm = new MockRMWithCustomAMLauncher(conf, null) { @Override protected ApplicationMasterLauncher createAMLauncher() { @@ -284,12 +281,8 @@ public class TestApplicationMasterLauncher { } }; } - - @Override - protected Dispatcher createDispatcher() { - return dispatcher; - } }; + rm.start(); MockNM nm1 = rm.registerNode("127.0.0.1:1234", 5120); @@ -297,7 +290,7 @@ public class TestApplicationMasterLauncher { // kick the scheduling nm1.nodeHeartbeat(true); - dispatcher.await(); + rm.drainEvents(); MockRM.waitForState(app.getCurrentAppAttempt(), RMAppAttemptState.LAUNCHED, 500); diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestApplicationMasterService.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestApplicationMasterService.java index 23bed228e19..18c49bdddcd 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestApplicationMasterService.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestApplicationMasterService.java @@ -42,8 +42,6 @@ import org.apache.hadoop.yarn.api.records.Priority; import org.apache.hadoop.yarn.api.records.ResourceRequest; import org.apache.hadoop.yarn.api.records.UpdateContainerRequest; import org.apache.hadoop.yarn.conf.YarnConfiguration; -import org.apache.hadoop.yarn.event.Dispatcher; -import org.apache.hadoop.yarn.event.DrainDispatcher; import org.apache.hadoop.yarn.exceptions.ApplicationMasterNotRegisteredException; import org.apache.hadoop.yarn.exceptions.InvalidContainerReleaseException; import org.apache.hadoop.yarn.proto.YarnServiceProtos.SchedulerResourceTypes; @@ -327,10 +325,8 @@ public class TestApplicationMasterService { @Test(timeout=1200000) public void testAllocateAfterUnregister() throws Exception { - MyResourceManager rm = new MyResourceManager(conf); + MockRM rm = new MockRM(conf); rm.start(); - DrainDispatcher rmDispatcher = (DrainDispatcher) rm.getRMContext() - .getDispatcher(); // Register node1 MockNM nm1 = rm.registerNode("127.0.0.1:1234", 6 * GB); @@ -351,7 +347,7 @@ public class TestApplicationMasterService { AllocateResponse alloc1Response = am1.schedule(); nm1.nodeHeartbeat(true); - rmDispatcher.await(); + rm.drainEvents(); alloc1Response = am1.schedule(); Assert.assertEquals(0, alloc1Response.getAllocatedContainers().size()); } @@ -474,17 +470,6 @@ public class TestApplicationMasterService { rm.stop(); } - private static class MyResourceManager extends MockRM { - - public MyResourceManager(YarnConfiguration conf) { - super(conf); - } - @Override - protected Dispatcher createDispatcher() { - return new DrainDispatcher(); - } - } - private void sentRMContainerLaunched(MockRM rm, ContainerId containerId) { CapacityScheduler cs = (CapacityScheduler) rm.getResourceScheduler(); RMContainer rmContainer = cs.getRMContainer(containerId); diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestNodeBlacklistingOnAMFailures.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestNodeBlacklistingOnAMFailures.java index b4adf480b35..75ef5c775be 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestNodeBlacklistingOnAMFailures.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestNodeBlacklistingOnAMFailures.java @@ -33,8 +33,6 @@ import org.apache.hadoop.yarn.api.records.Priority; import org.apache.hadoop.yarn.api.records.Resource; import org.apache.hadoop.yarn.api.records.ResourceRequest; import org.apache.hadoop.yarn.conf.YarnConfiguration; -import org.apache.hadoop.yarn.event.Dispatcher; -import org.apache.hadoop.yarn.event.DrainDispatcher; import org.apache.hadoop.yarn.server.resourcemanager.applicationsmanager.TestAMRestart; import org.apache.hadoop.yarn.server.resourcemanager.recovery.MemoryRMStateStore; import org.apache.hadoop.yarn.server.resourcemanager.rmapp.RMApp; @@ -65,8 +63,7 @@ public class TestNodeBlacklistingOnAMFailures { conf.setBoolean(YarnConfiguration.AM_SCHEDULING_NODE_BLACKLISTING_ENABLED, true); - DrainDispatcher dispatcher = new DrainDispatcher(); - MockRM rm = startRM(conf, dispatcher); + MockRM rm = startRM(conf); CapacityScheduler scheduler = (CapacityScheduler) rm.getResourceScheduler(); // Register 5 nodes, so that we can blacklist atleast one if AM container @@ -122,7 +119,7 @@ public class TestNodeBlacklistingOnAMFailures { // Try the current node a few times for (int i = 0; i <= 2; i++) { currentNode.nodeHeartbeat(true); - dispatcher.await(); + rm.drainEvents(); Assert.assertEquals( "AppAttemptState should still be SCHEDULED if currentNode is " @@ -132,7 +129,7 @@ public class TestNodeBlacklistingOnAMFailures { // Now try the other node otherNode.nodeHeartbeat(true); - dispatcher.await(); + rm.drainEvents(); // Now the AM container should be allocated MockRM.waitForState(attempt, RMAppAttemptState.ALLOCATED, 20000); @@ -169,8 +166,7 @@ public class TestNodeBlacklistingOnAMFailures { conf.setBoolean(YarnConfiguration.AM_SCHEDULING_NODE_BLACKLISTING_ENABLED, true); - DrainDispatcher dispatcher = new DrainDispatcher(); - MockRM rm = startRM(conf, dispatcher); + MockRM rm = startRM(conf); CapacityScheduler scheduler = (CapacityScheduler) rm.getResourceScheduler(); // Register 5 nodes, so that we can blacklist atleast one if AM container @@ -227,7 +223,7 @@ public class TestNodeBlacklistingOnAMFailures { System.out.println("New AppAttempt launched " + attempt.getAppAttemptId()); nm2.nodeHeartbeat(true); - dispatcher.await(); + rm.drainEvents(); // Now the AM container should be allocated MockRM.waitForState(attempt, RMAppAttemptState.ALLOCATED, 20000); @@ -257,8 +253,7 @@ public class TestNodeBlacklistingOnAMFailures { conf.setBoolean(YarnConfiguration.AM_SCHEDULING_NODE_BLACKLISTING_ENABLED, true); - DrainDispatcher dispatcher = new DrainDispatcher(); - MockRM rm = startRM(conf, dispatcher); + MockRM rm = startRM(conf); CapacityScheduler scheduler = (CapacityScheduler) rm.getResourceScheduler(); // Register 5 nodes, so that we can blacklist atleast one if AM container @@ -319,7 +314,7 @@ public class TestNodeBlacklistingOnAMFailures { nm3.nodeHeartbeat(true); nm4.nodeHeartbeat(true); nm5.nodeHeartbeat(true); - dispatcher.await(); + rm.drainEvents(); // Now the AM container should be allocated MockRM.waitForState(attempt, RMAppAttemptState.ALLOCATED, 20000); @@ -352,8 +347,7 @@ public class TestNodeBlacklistingOnAMFailures { 1.5f); conf.setInt(YarnConfiguration.RM_AM_MAX_ATTEMPTS, 100); - DrainDispatcher dispatcher = new DrainDispatcher(); - MockRM rm = startRM(conf, dispatcher); + MockRM rm = startRM(conf); MockNM node = new MockNM("127.0.0.1:1234", 8000, rm.getResourceTrackerService()); @@ -367,7 +361,7 @@ public class TestNodeBlacklistingOnAMFailures { // Now the AM container should be allocated RMAppAttempt attempt = MockRM.waitForAttemptScheduled(app, rm); node.nodeHeartbeat(true); - dispatcher.await(); + rm.drainEvents(); MockRM.waitForState(attempt, RMAppAttemptState.ALLOCATED, 20000); rm.sendAMLaunched(attempt.getAppAttemptId()); rm.waitForState(attempt.getAppAttemptId(), RMAppAttemptState.LAUNCHED); @@ -394,7 +388,7 @@ public class TestNodeBlacklistingOnAMFailures { .println("New AppAttempt launched " + attempt.getAppAttemptId()); node.nodeHeartbeat(true); - dispatcher.await(); + rm.drainEvents(); MockRM.waitForState(attempt, RMAppAttemptState.ALLOCATED, 20000); rm.sendAMLaunched(attempt.getAppAttemptId()); @@ -418,20 +412,13 @@ public class TestNodeBlacklistingOnAMFailures { rm.waitForState(amAttemptID.getApplicationId(), RMAppState.ACCEPTED); } - private MockRM startRM(YarnConfiguration conf, - final DrainDispatcher dispatcher) { - + private MockRM startRM(YarnConfiguration conf) { MemoryRMStateStore memStore = new MemoryRMStateStore(); memStore.init(conf); - MockRM rm1 = new MockRM(conf, memStore) { - @Override - protected Dispatcher createDispatcher() { - return dispatcher; - } - }; + MockRM rm = new MockRM(conf, memStore); - rm1.start(); - return rm1; + rm.start(); + return rm; } } diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestReservationSystemWithRMHA.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestReservationSystemWithRMHA.java index 5a6fe67e2b1..f746dc2f188 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestReservationSystemWithRMHA.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestReservationSystemWithRMHA.java @@ -29,7 +29,6 @@ import org.apache.hadoop.yarn.api.records.QueueInfo; import org.apache.hadoop.yarn.api.records.ReservationDefinition; import org.apache.hadoop.yarn.api.records.ReservationId; import org.apache.hadoop.yarn.conf.YarnConfiguration; -import org.apache.hadoop.yarn.event.DrainDispatcher; import org.apache.hadoop.yarn.proto.YarnProtos.ReservationAllocationStateProto; import org.apache.hadoop.yarn.server.resourcemanager.recovery.RMStateStore.RMState; import org.apache.hadoop.yarn.server.resourcemanager.reservation.Plan; @@ -186,9 +185,7 @@ public class TestReservationSystemWithRMHA extends RMHATestBase { rm.registerNode("127.0.0.1:1", memory, vCores); int attempts = 10; do { - DrainDispatcher dispatcher = - (DrainDispatcher) rm1.getRMContext().getDispatcher(); - dispatcher.await(); + rm1.drainEvents(); rm.getRMContext().getReservationSystem() .synchronizePlan(ReservationSystemTestUtil.reservationQ, false); if (rm.getRMContext().getReservationSystem() diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/applicationsmanager/TestAMRMRPCNodeUpdates.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/applicationsmanager/TestAMRMRPCNodeUpdates.java index c8baa607bf0..f9f0b746233 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/applicationsmanager/TestAMRMRPCNodeUpdates.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/applicationsmanager/TestAMRMRPCNodeUpdates.java @@ -31,8 +31,6 @@ import org.apache.hadoop.yarn.api.protocolrecords.AllocateResponse; import org.apache.hadoop.yarn.api.records.ApplicationAttemptId; import org.apache.hadoop.yarn.api.records.NodeReport; import org.apache.hadoop.yarn.api.records.NodeState; -import org.apache.hadoop.yarn.event.Dispatcher; -import org.apache.hadoop.yarn.event.DrainDispatcher; import org.apache.hadoop.yarn.security.AMRMTokenIdentifier; import org.apache.hadoop.yarn.server.resourcemanager.ApplicationMasterService; import org.apache.hadoop.yarn.server.resourcemanager.MockAM; @@ -47,12 +45,10 @@ import org.junit.Test; public class TestAMRMRPCNodeUpdates { private MockRM rm; - ApplicationMasterService amService = null; - DrainDispatcher dispatcher = null; + private ApplicationMasterService amService; @Before public void setUp() { - dispatcher = new DrainDispatcher(); this.rm = new MockRM() { @Override public void init(Configuration conf) { @@ -61,12 +57,8 @@ public class TestAMRMRPCNodeUpdates { "1.0"); super.init(conf); } - - @Override - protected Dispatcher createDispatcher() { - return dispatcher; - } }; + rm.start(); amService = rm.getApplicationMasterService(); } @@ -80,14 +72,14 @@ public class TestAMRMRPCNodeUpdates { private void syncNodeHeartbeat(MockNM nm, boolean health) throws Exception { nm.nodeHeartbeat(health); - dispatcher.await(); + rm.drainEvents(); } private void syncNodeLost(MockNM nm) throws Exception { rm.sendNodeStarted(nm); rm.waitForState(nm.getNodeId(), NodeState.RUNNING); rm.sendNodeLost(nm); - dispatcher.await(); + rm.drainEvents(); } private AllocateResponse allocate(final ApplicationAttemptId attemptId, @@ -113,7 +105,7 @@ public class TestAMRMRPCNodeUpdates { MockNM nm2 = rm.registerNode("127.0.0.2:1234", 10000); MockNM nm3 = rm.registerNode("127.0.0.3:1234", 10000); MockNM nm4 = rm.registerNode("127.0.0.4:1234", 10000); - dispatcher.await(); + rm.drainEvents(); RMApp app1 = rm.submitApp(2000); diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/resourcetracker/TestNMReconnect.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/resourcetracker/TestNMReconnect.java index e7c7e51bf2c..6a7325c25c8 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/resourcetracker/TestNMReconnect.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/resourcetracker/TestNMReconnect.java @@ -28,7 +28,6 @@ import org.apache.hadoop.yarn.conf.ConfigurationProvider; import org.apache.hadoop.yarn.conf.ConfigurationProviderFactory; import org.apache.hadoop.yarn.conf.YarnConfiguration; import org.apache.hadoop.yarn.event.Dispatcher; -import org.apache.hadoop.yarn.event.DrainDispatcher; import org.apache.hadoop.yarn.event.EventHandler; import org.apache.hadoop.yarn.event.InlineDispatcher; import org.apache.hadoop.yarn.factories.RecordFactory; @@ -228,21 +227,16 @@ public class TestNMReconnect extends ParameterizedSchedulerTestBase { // The node(127.0.0.1:1234) reconnected with RM. When it registered with // RM, RM set its lastNodeHeartbeatResponse's id to 0 asynchronously. But // the node's heartbeat come before RM succeeded setting the id to 0. - final DrainDispatcher dispatcher = new DrainDispatcher(); - MockRM rm = new MockRM(){ - @Override - protected Dispatcher createDispatcher() { - return dispatcher; - } - }; + MockRM rm = new MockRM(); rm.start(); + MockNM nm1 = new MockNM("127.0.0.1:1234", 15120, rm.getResourceTrackerService()); nm1.registerNode(); int i = 0; while(i < 3) { nm1.nodeHeartbeat(true); - dispatcher.await(); + rm.drainEvents(); i++; } @@ -251,7 +245,7 @@ public class TestNMReconnect extends ParameterizedSchedulerTestBase { nm2.registerNode(); RMNode rmNode = rm.getRMContext().getRMNodes().get(nm2.getNodeId()); nm2.nodeHeartbeat(true); - dispatcher.await(); + rm.drainEvents(); Assert.assertEquals("Node is Not in Running state.", NodeState.RUNNING, rmNode.getState()); rm.stop(); diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/rmcontainer/TestRMContainerImpl.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/rmcontainer/TestRMContainerImpl.java index 893f802ade3..db3144898fd 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/rmcontainer/TestRMContainerImpl.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/rmcontainer/TestRMContainerImpl.java @@ -46,7 +46,6 @@ import org.apache.hadoop.yarn.api.records.Priority; import org.apache.hadoop.yarn.api.records.Resource; import org.apache.hadoop.yarn.api.records.ResourceRequest; import org.apache.hadoop.yarn.conf.YarnConfiguration; -import org.apache.hadoop.yarn.event.Dispatcher; import org.apache.hadoop.yarn.event.DrainDispatcher; import org.apache.hadoop.yarn.event.EventHandler; import org.apache.hadoop.yarn.server.resourcemanager.MockAM; diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/TestApplicationPriority.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/TestApplicationPriority.java index ff52efd89be..fd17bd91a3d 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/TestApplicationPriority.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/TestApplicationPriority.java @@ -36,8 +36,6 @@ import org.apache.hadoop.yarn.api.records.Container; import org.apache.hadoop.yarn.api.records.ContainerId; import org.apache.hadoop.yarn.api.records.Priority; import org.apache.hadoop.yarn.conf.YarnConfiguration; -import org.apache.hadoop.yarn.event.Dispatcher; -import org.apache.hadoop.yarn.event.DrainDispatcher; import org.apache.hadoop.yarn.exceptions.YarnException; import org.apache.hadoop.yarn.server.resourcemanager.MockAM; import org.apache.hadoop.yarn.server.resourcemanager.MockNM; @@ -612,24 +610,17 @@ public class TestApplicationPriority { conf.setInt(YarnConfiguration.RM_AM_MAX_ATTEMPTS, YarnConfiguration.DEFAULT_RM_AM_MAX_ATTEMPTS); conf.setInt(YarnConfiguration.MAX_CLUSTER_LEVEL_APPLICATION_PRIORITY, 10); - final DrainDispatcher dispatcher = new DrainDispatcher(); MemoryRMStateStore memStore = new MemoryRMStateStore(); memStore.init(conf); - MockRM rm1 = new MockRM(conf, memStore) { - @Override - protected Dispatcher createDispatcher() { - return dispatcher; - } - }; + MockRM rm1 = new MockRM(conf, memStore); rm1.start(); MockNM nm1 = new MockNM("127.0.0.1:1234", 16384, rm1.getResourceTrackerService()); nm1.registerNode(); - - dispatcher.await(); + rm1.drainEvents(); ResourceScheduler scheduler = rm1.getRMContext().getScheduler(); LeafQueue defaultQueue = @@ -648,7 +639,7 @@ public class TestApplicationPriority { MockAM am2 = MockRM.launchAM(app2, rm1, nm1); am2.registerAppAttempt(); - dispatcher.await(); + rm1.drainEvents(); Assert.assertEquals(2, defaultQueue.getNumActiveApplications()); Assert.assertEquals(0, defaultQueue.getNumPendingApplications()); @@ -657,7 +648,7 @@ public class TestApplicationPriority { Priority appPriority3 = Priority.newInstance(7); RMApp app3 = rm1.submitApp(memory, appPriority3); - dispatcher.await(); + rm1.drainEvents(); Assert.assertEquals(2, defaultQueue.getNumActiveApplications()); Assert.assertEquals(1, defaultQueue.getNumPendingApplications()); @@ -676,14 +667,8 @@ public class TestApplicationPriority { Assert.assertEquals(app3.getCurrentAppAttempt().getAppAttemptId(), fcApp3.getApplicationAttemptId()); - final DrainDispatcher dispatcher1 = new DrainDispatcher(); // create new RM to represent restart and recover state - MockRM rm2 = new MockRM(conf, memStore) { - @Override - protected Dispatcher createDispatcher() { - return dispatcher1; - } - }; + MockRM rm2 = new MockRM(conf, memStore); // start new RM rm2.start(); @@ -693,7 +678,7 @@ public class TestApplicationPriority { // Verify RM Apps after this restart Assert.assertEquals(3, rm2.getRMContext().getRMApps().size()); - dispatcher1.await(); + rm2.drainEvents(); scheduler = rm2.getRMContext().getScheduler(); defaultQueue = (LeafQueue) ((CapacityScheduler) scheduler).getQueue("default"); @@ -714,7 +699,7 @@ public class TestApplicationPriority { // NM resync to new RM nm1.registerNode(); - dispatcher1.await(); + rm2.drainEvents(); // wait for activating applications count = 50; diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/security/TestClientToAMTokens.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/security/TestClientToAMTokens.java index a4513561d2c..d4e7727ad5e 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/security/TestClientToAMTokens.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/security/TestClientToAMTokens.java @@ -50,8 +50,6 @@ import org.apache.hadoop.yarn.api.protocolrecords.StartContainersResponse; import org.apache.hadoop.yarn.api.records.ApplicationAttemptId; import org.apache.hadoop.yarn.api.records.ApplicationReport; import org.apache.hadoop.yarn.conf.YarnConfiguration; -import org.apache.hadoop.yarn.event.Dispatcher; -import org.apache.hadoop.yarn.event.DrainDispatcher; import org.apache.hadoop.yarn.exceptions.YarnRuntimeException; import org.apache.hadoop.yarn.security.client.ClientToAMTokenIdentifier; import org.apache.hadoop.yarn.security.client.ClientToAMTokenSecretManager; @@ -199,7 +197,6 @@ public class TestClientToAMTokens extends ParameterizedSchedulerTestBase { StartContainersResponse mockResponse = mock(StartContainersResponse.class); when(containerManager.startContainers((StartContainersRequest) any())) .thenReturn(mockResponse); - final DrainDispatcher dispatcher = new DrainDispatcher(); MockRM rm = new MockRMWithCustomAMLauncher(conf, containerManager) { protected ClientRMService createClientRMService() { @@ -208,11 +205,6 @@ public class TestClientToAMTokens extends ParameterizedSchedulerTestBase { getRMContext().getRMDelegationTokenSecretManager()); }; - @Override - protected Dispatcher createDispatcher() { - return dispatcher; - } - @Override protected void doSecureLogin() throws IOException { } @@ -225,11 +217,10 @@ public class TestClientToAMTokens extends ParameterizedSchedulerTestBase { // Set up a node. MockNM nm1 = rm.registerNode("localhost:1234", 3072); nm1.nodeHeartbeat(true); - dispatcher.await(); + rm.drainEvents(); - nm1.nodeHeartbeat(true); - dispatcher.await(); + rm.drainEvents(); ApplicationAttemptId appAttempt = app.getCurrentAppAttempt().getAppAttemptId(); final MockAM mockAM = @@ -436,7 +427,6 @@ public class TestClientToAMTokens extends ParameterizedSchedulerTestBase { StartContainersResponse mockResponse = mock(StartContainersResponse.class); when(containerManager.startContainers((StartContainersRequest) any())) .thenReturn(mockResponse); - final DrainDispatcher dispatcher = new DrainDispatcher(); MockRM rm = new MockRMWithCustomAMLauncher(conf, containerManager) { protected ClientRMService createClientRMService() { @@ -445,11 +435,6 @@ public class TestClientToAMTokens extends ParameterizedSchedulerTestBase { getRMContext().getRMDelegationTokenSecretManager()); }; - @Override - protected Dispatcher createDispatcher() { - return dispatcher; - } - @Override protected void doSecureLogin() throws IOException { } @@ -462,10 +447,10 @@ public class TestClientToAMTokens extends ParameterizedSchedulerTestBase { // Set up a node. MockNM nm1 = rm.registerNode("localhost:1234", 3072); nm1.nodeHeartbeat(true); - dispatcher.await(); + rm.drainEvents(); nm1.nodeHeartbeat(true); - dispatcher.await(); + rm.drainEvents(); ApplicationAttemptId appAttempt = app.getCurrentAppAttempt().getAppAttemptId(); final MockAM mockAM = From 318bfb01bc6793da09e32e9cc292eb63224b6ca2 Mon Sep 17 00:00:00 2001 From: Eric Payne Date: Fri, 31 Mar 2017 12:30:35 -0500 Subject: [PATCH 176/188] YARN-6354. LeveldbRMStateStore can parse invalid keys when recovering reservations. Contributed by Jason Lowe --- .../recovery/LeveldbRMStateStore.java | 8 ++++++++ .../recovery/TestLeveldbRMStateStore.java | 19 +++++++++++++++---- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/recovery/LeveldbRMStateStore.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/recovery/LeveldbRMStateStore.java index 02f90ddb9af..2ca53db2b34 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/recovery/LeveldbRMStateStore.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/recovery/LeveldbRMStateStore.java @@ -214,6 +214,11 @@ public class LeveldbRMStateStore extends RMStateStore { return db == null; } + @VisibleForTesting + DB getDatabase() { + return db; + } + @Override protected Version loadVersion() throws Exception { Version version = null; @@ -284,6 +289,9 @@ public class LeveldbRMStateStore extends RMStateStore { while (iter.hasNext()) { Entry entry = iter.next(); String key = asString(entry.getKey()); + if (!key.startsWith(RM_RESERVATION_KEY_PREFIX)) { + break; + } String planReservationString = key.substring(RM_RESERVATION_KEY_PREFIX.length()); diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/recovery/TestLeveldbRMStateStore.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/recovery/TestLeveldbRMStateStore.java index 4297e732e77..51adbe189bc 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/recovery/TestLeveldbRMStateStore.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/recovery/TestLeveldbRMStateStore.java @@ -31,6 +31,7 @@ import org.apache.hadoop.yarn.conf.YarnConfiguration; import org.apache.hadoop.yarn.server.records.Version; import org.apache.hadoop.yarn.server.resourcemanager.rmapp.RMApp; import org.apache.hadoop.yarn.server.resourcemanager.rmapp.attempt.RMAppAttempt; +import org.fusesource.leveldbjni.JniDBFactory; import org.iq80.leveldb.DB; import org.junit.After; import org.junit.Before; @@ -125,17 +126,27 @@ public class TestLeveldbRMStateStore extends RMStateStoreTestBase { public void testCompactionCycle() throws Exception { final DB mockdb = mock(DB.class); conf.setLong(YarnConfiguration.RM_LEVELDB_COMPACTION_INTERVAL_SECS, 1); - LeveldbRMStateStore store = new LeveldbRMStateStore() { + stateStore = new LeveldbRMStateStore() { @Override protected DB openDatabase() throws Exception { return mockdb; } }; - store.init(conf); - store.start(); + stateStore.init(conf); + stateStore.start(); verify(mockdb, timeout(10000)).compactRange( (byte[]) isNull(), (byte[]) isNull()); - store.close(); + } + + @Test + public void testBadKeyIteration() throws Exception { + stateStore = new LeveldbRMStateStore(); + stateStore.init(conf); + stateStore.start(); + DB db = stateStore.getDatabase(); + // add an entry that appears at the end of the database when iterating + db.put(JniDBFactory.bytes("zzz"), JniDBFactory.bytes("z")); + stateStore.loadState(); } class LeveldbStateStoreTester implements RMStateStoreHelper { From 5485d93bda3329a7c80767c3723cc6e1a9233dbc Mon Sep 17 00:00:00 2001 From: Arpit Agarwal Date: Fri, 31 Mar 2017 12:10:20 -0700 Subject: [PATCH 177/188] HDFS-11603. Improve slow mirror/disk warnings in BlockReceiver. --- .../hadoop/hdfs/protocol/DatanodeInfo.java | 1 + .../hdfs/server/datanode/BlockReceiver.java | 61 ++++++++++++++----- 2 files changed, 47 insertions(+), 15 deletions(-) diff --git a/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/protocol/DatanodeInfo.java b/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/protocol/DatanodeInfo.java index e1698c98c03..0a8c9151f1c 100644 --- a/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/protocol/DatanodeInfo.java +++ b/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/protocol/DatanodeInfo.java @@ -55,6 +55,7 @@ public class DatanodeInfo extends DatanodeID implements Node { private String softwareVersion; private List dependentHostNames = new LinkedList<>(); private String upgradeDomain; + public static final DatanodeInfo[] EMPTY_ARRAY = {}; // Datanode administrative states public enum AdminStates { diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/BlockReceiver.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/BlockReceiver.java index bb6bd5570e2..00109e052d9 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/BlockReceiver.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/BlockReceiver.java @@ -100,6 +100,7 @@ class BlockReceiver implements Closeable { private DataTransferThrottler throttler; private ReplicaOutputStreams streams; private DatanodeInfo srcDataNode = null; + private DatanodeInfo[] downstreamDNs = DatanodeInfo.EMPTY_ARRAY; private final DataNode datanode; volatile private boolean mirrorError; @@ -424,10 +425,10 @@ class BlockReceiver implements Closeable { } } long duration = Time.monotonicNow() - begin; - if (duration > datanodeSlowLogThresholdMs) { + if (duration > datanodeSlowLogThresholdMs && LOG.isWarnEnabled()) { LOG.warn("Slow flushOrSync took " + duration + "ms (threshold=" + datanodeSlowLogThresholdMs + "ms), isSync:" + isSync + ", flushTotalNanos=" - + flushTotalNanos + "ns"); + + flushTotalNanos + "ns, volume=" + getVolumeBaseUri()); } } @@ -578,9 +579,10 @@ class BlockReceiver implements Closeable { mirrorAddr, duration); trackSendPacketToLastNodeInPipeline(duration); - if (duration > datanodeSlowLogThresholdMs) { + if (duration > datanodeSlowLogThresholdMs && LOG.isWarnEnabled()) { LOG.warn("Slow BlockReceiver write packet to mirror took " + duration - + "ms (threshold=" + datanodeSlowLogThresholdMs + "ms)"); + + "ms (threshold=" + datanodeSlowLogThresholdMs + "ms), " + + "downstream DNs=" + Arrays.toString(downstreamDNs)); } } catch (IOException e) { handleMirrorOutError(e); @@ -711,9 +713,10 @@ class BlockReceiver implements Closeable { streams.writeDataToDisk(dataBuf.array(), startByteToDisk, numBytesToDisk); long duration = Time.monotonicNow() - begin; - if (duration > datanodeSlowLogThresholdMs) { + if (duration > datanodeSlowLogThresholdMs && LOG.isWarnEnabled()) { LOG.warn("Slow BlockReceiver write data to disk cost:" + duration - + "ms (threshold=" + datanodeSlowLogThresholdMs + "ms)"); + + "ms (threshold=" + datanodeSlowLogThresholdMs + "ms), " + + "volume=" + getVolumeBaseUri()); } if (duration > maxWriteToDiskMs) { @@ -902,9 +905,10 @@ class BlockReceiver implements Closeable { } lastCacheManagementOffset = offsetInBlock; long duration = Time.monotonicNow() - begin; - if (duration > datanodeSlowLogThresholdMs) { + if (duration > datanodeSlowLogThresholdMs && LOG.isWarnEnabled()) { LOG.warn("Slow manageWriterOsCache took " + duration - + "ms (threshold=" + datanodeSlowLogThresholdMs + "ms)"); + + "ms (threshold=" + datanodeSlowLogThresholdMs + + "ms), volume=" + getVolumeBaseUri()); } } } catch (Throwable t) { @@ -932,13 +936,7 @@ class BlockReceiver implements Closeable { boolean responderClosed = false; mirrorOut = mirrOut; mirrorAddr = mirrAddr; - isPenultimateNode = ((downstreams != null) && (downstreams.length == 1)); - if (isPenultimateNode) { - mirrorNameForMetrics = (downstreams[0].getInfoSecurePort() != 0 ? - downstreams[0].getInfoSecureAddr() : downstreams[0].getInfoAddr()); - LOG.debug("Will collect peer metrics for downstream node {}", - mirrorNameForMetrics); - } + initPerfMonitoring(downstreams); throttler = throttlerArg; this.replyOut = replyOut; @@ -1058,6 +1056,39 @@ class BlockReceiver implements Closeable { } } + /** + * If we have downstream DNs and peerMetrics are enabled, then initialize + * some state for monitoring the performance of downstream DNs. + * + * @param downstreams downstream DNs, or null if there are none. + */ + private void initPerfMonitoring(DatanodeInfo[] downstreams) { + if (downstreams != null && downstreams.length > 0) { + downstreamDNs = downstreams; + isPenultimateNode = (downstreams.length == 1); + if (isPenultimateNode && datanode.getPeerMetrics() != null) { + mirrorNameForMetrics = (downstreams[0].getInfoSecurePort() != 0 ? + downstreams[0].getInfoSecureAddr() : downstreams[0].getInfoAddr()); + LOG.debug("Will collect peer metrics for downstream node {}", + mirrorNameForMetrics); + } + } + } + + /** + * Fetch the base URI of the volume on which this replica resides. + * + * @returns Volume base URI as string if available. Else returns the + * the string "unavailable". + */ + private String getVolumeBaseUri() { + final ReplicaInfo ri = replicaInfo.getReplicaInfo(); + if (ri != null && ri.getVolume() != null) { + return ri.getVolume().getBaseURI().toString(); + } + return "unavailable"; + } + /** Cleanup a partial block * if this write is for a replication request (and not from a client) */ From 73835c73e2d34b3854a71dd29d88c8303d698ac8 Mon Sep 17 00:00:00 2001 From: Hanisha Koneru Date: Fri, 31 Mar 2017 13:50:29 -0700 Subject: [PATCH 178/188] HDFS-11560. Expose slow disks via NameNode JMX. Contributed by Hanisha Koneru. --- .../blockmanagement/DatanodeManager.java | 9 +++ .../blockmanagement/SlowDiskTracker.java | 3 + .../hadoop/hdfs/server/namenode/NameNode.java | 6 ++ .../server/namenode/NameNodeStatusMXBean.java | 8 +++ .../blockmanagement/TestSlowDiskTracker.java | 13 +--- .../namenode/TestNameNodeStatusMXBean.java | 59 ++++++++++++++++++- 6 files changed, 85 insertions(+), 13 deletions(-) diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/blockmanagement/DatanodeManager.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/blockmanagement/DatanodeManager.java index 18135a8a549..c7bdca9c155 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/blockmanagement/DatanodeManager.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/blockmanagement/DatanodeManager.java @@ -1907,5 +1907,14 @@ public class DatanodeManager { public SlowDiskTracker getSlowDiskTracker() { return slowDiskTracker; } + /** + * Retrieve information about slow disks as a JSON. + * Returns null if we are not tracking slow disks. + * @return + */ + public String getSlowDisksReport() { + return slowDiskTracker != null ? + slowDiskTracker.getSlowDiskReportAsJsonString() : null; + } } diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/blockmanagement/SlowDiskTracker.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/blockmanagement/SlowDiskTracker.java index 25920a2e07f..52fce5d35c0 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/blockmanagement/SlowDiskTracker.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/blockmanagement/SlowDiskTracker.java @@ -256,6 +256,9 @@ public class SlowDiskTracker { public String getSlowDiskReportAsJsonString() { ObjectMapper objectMapper = new ObjectMapper(); try { + if (slowDisksReport.isEmpty()) { + return null; + } return objectMapper.writeValueAsString(slowDisksReport); } catch (JsonProcessingException e) { // Failed to serialize. Don't log the exception call stack. diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/NameNode.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/NameNode.java index e7841f02a9e..32d268a9dbf 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/NameNode.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/NameNode.java @@ -1826,6 +1826,12 @@ public class NameNode extends ReconfigurableBase implements .getSlowPeersReport(); } + @Override //NameNodeStatusMXBean + public String getSlowDisksReport() { + return namesystem.getBlockManager().getDatanodeManager() + .getSlowDisksReport(); + } + /** * Shutdown the NN immediately in an ungraceful way. Used when it would be * unsafe for the NN to continue operating, e.g. during a failed HA state diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/NameNodeStatusMXBean.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/NameNodeStatusMXBean.java index f46b9ae927e..ed1b96bdf11 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/NameNodeStatusMXBean.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/NameNodeStatusMXBean.java @@ -75,4 +75,12 @@ public interface NameNodeStatusMXBean { * enabled. The report is in a JSON format. */ String getSlowPeersReport(); + + + /** + * Gets the topN slow disks in the cluster, if the feature is enabled. + * + * @return JSON string of list of diskIDs and latencies + */ + String getSlowDisksReport(); } diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/blockmanagement/TestSlowDiskTracker.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/blockmanagement/TestSlowDiskTracker.java index e96b96a25af..16dfab208ad 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/blockmanagement/TestSlowDiskTracker.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/blockmanagement/TestSlowDiskTracker.java @@ -393,18 +393,9 @@ public class TestSlowDiskTracker { timer.advance(reportValidityMs); tracker.updateSlowDiskReportAsync(timer.monotonicNow()); + Thread.sleep(OUTLIERS_REPORT_INTERVAL*2); - GenericTestUtils.waitFor(new Supplier() { - @Override - public Boolean get() { - return tracker.getSlowDiskReportAsJsonString() != null; - } - }, 500, 5000); - - ArrayList jsonReport = getAndDeserializeJson( - tracker.getSlowDiskReportAsJsonString()); - - assertTrue(jsonReport.isEmpty()); + assertTrue(tracker.getSlowDiskReportAsJsonString() == null); } private boolean isDiskInReports(ArrayList reports, diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestNameNodeStatusMXBean.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestNameNodeStatusMXBean.java index c03dc209f9d..8fe734e44bc 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestNameNodeStatusMXBean.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestNameNodeStatusMXBean.java @@ -17,17 +17,23 @@ */ package org.apache.hadoop.hdfs.server.namenode; +import com.google.common.base.Supplier; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hdfs.server.blockmanagement.DatanodeManager; +import org.apache.hadoop.hdfs.server.datanode.DataNode; +import org.apache.hadoop.hdfs.DFSConfigKeys; import org.apache.hadoop.hdfs.MiniDFSCluster; -import org.apache.hadoop.hdfs.server.datanode.TestDataNodeMXBean; +import org.apache.hadoop.test.GenericTestUtils; import org.junit.Assert; import org.junit.Test; import javax.management.MBeanServer; import javax.management.ObjectName; import java.lang.management.ManagementFactory; +import java.util.List; +import java.util.concurrent.TimeUnit; /** * Class for testing {@link NameNodeStatusMXBean} implementation. @@ -38,7 +44,7 @@ public class TestNameNodeStatusMXBean { TestNameNodeStatusMXBean.class); @Test(timeout = 120000L) - public void testDataNodeMXBean() throws Exception { + public void testNameNodeStatusMXBean() throws Exception { Configuration conf = new Configuration(); MiniDFSCluster cluster = null; @@ -84,6 +90,55 @@ public class TestNameNodeStatusMXBean { String slowPeersReport = (String)mbs.getAttribute(mxbeanName, "SlowPeersReport"); Assert.assertEquals(nn.getSlowPeersReport(), slowPeersReport); + + // Get attribute "SlowDisksReport" + String slowDisksReport = (String)mbs.getAttribute(mxbeanName, + "SlowDisksReport"); + Assert.assertEquals(nn.getSlowDisksReport(), slowDisksReport); + } finally { + if (cluster != null) { + cluster.shutdown(); + } + } + } + + @Test + public void testNameNodeMXBeanSlowDisksEnabled() throws Exception { + Configuration conf = new Configuration(); + conf.setDouble( + DFSConfigKeys.DFS_DATANODE_FILEIO_PROFILING_SAMPLING_FRACTION_KEY, 1.0); + conf.setTimeDuration( + DFSConfigKeys.DFS_DATANODE_OUTLIERS_REPORT_INTERVAL_KEY, + 1000, TimeUnit.MILLISECONDS); + MiniDFSCluster cluster = new MiniDFSCluster.Builder(conf).build(); + + try { + List datanodes = cluster.getDataNodes(); + Assert.assertEquals(datanodes.size(), 1); + DataNode datanode = datanodes.get(0); + String slowDiskPath = "test/data1/slowVolume"; + datanode.getDiskMetrics().addSlowDiskForTesting(slowDiskPath, null); + + NameNode nn = cluster.getNameNode(); + DatanodeManager datanodeManager = nn.getNamesystem().getBlockManager() + .getDatanodeManager(); + + MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); + ObjectName mxbeanName = new ObjectName( + "Hadoop:service=NameNode,name=NameNodeStatus"); + + GenericTestUtils.waitFor(new Supplier() { + @Override + public Boolean get() { + return (datanodeManager.getSlowDisksReport() != null); + } + }, 1000, 100000); + + String slowDisksReport = (String)mbs.getAttribute( + mxbeanName, "SlowDisksReport"); + Assert.assertEquals(datanodeManager.getSlowDisksReport(), + slowDisksReport); + Assert.assertTrue(slowDisksReport.contains(slowDiskPath)); } finally { if (cluster != null) { cluster.shutdown(); From 26172a94d6431e70d7fe15d66be9a7e195f79f60 Mon Sep 17 00:00:00 2001 From: Mingliang Liu Date: Thu, 23 Jun 2016 00:21:49 -0700 Subject: [PATCH 179/188] HADOOP-14267. Make DistCpOptions immutable. Contributed by Mingliang Liu --- .../org/apache/hadoop/tools/CopyListing.java | 38 +- .../java/org/apache/hadoop/tools/DistCp.java | 106 +- .../apache/hadoop/tools/DistCpContext.java | 198 ++++ .../hadoop/tools/DistCpOptionSwitch.java | 2 +- .../apache/hadoop/tools/DistCpOptions.java | 925 ++++++++---------- .../org/apache/hadoop/tools/DistCpSync.java | 42 +- .../hadoop/tools/FileBasedCopyListing.java | 12 +- .../hadoop/tools/GlobbedCopyListing.java | 17 +- .../apache/hadoop/tools/OptionsParser.java | 307 ++---- .../hadoop/tools/SimpleCopyListing.java | 115 ++- .../hadoop/tools/mapred/CopyCommitter.java | 15 +- .../apache/hadoop/tools/util/DistCpUtils.java | 8 +- .../apache/hadoop/tools/TestCopyListing.java | 51 +- .../hadoop/tools/TestDistCpOptions.java | 500 ++++++++++ .../apache/hadoop/tools/TestDistCpSync.java | 68 +- .../tools/TestDistCpSyncReverseBase.java | 44 +- .../apache/hadoop/tools/TestDistCpViewFs.java | 10 +- .../tools/TestFileBasedCopyListing.java | 9 +- .../hadoop/tools/TestGlobbedCopyListing.java | 11 +- .../apache/hadoop/tools/TestIntegration.java | 20 +- .../hadoop/tools/TestOptionsParser.java | 81 +- .../contract/AbstractContractDistCpTest.java | 6 +- .../tools/mapred/TestCopyCommitter.java | 34 +- .../mapred/TestUniformSizeInputFormat.java | 15 +- .../mapred/lib/TestDynamicInputFormat.java | 17 +- 25 files changed, 1578 insertions(+), 1073 deletions(-) create mode 100644 hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCpContext.java create mode 100644 hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestDistCpOptions.java diff --git a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/CopyListing.java b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/CopyListing.java index 9ebf9d29b3b..908b5580476 100644 --- a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/CopyListing.java +++ b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/CopyListing.java @@ -77,41 +77,41 @@ public abstract class CopyListing extends Configured { * TARGET IS DIR : Key-"/file1", Value-FileStatus(/tmp/file1) * * @param pathToListFile - Output file where the listing would be stored - * @param options - Input options to distcp + * @param distCpContext - distcp context associated with input options * @throws IOException - Exception if any */ public final void buildListing(Path pathToListFile, - DistCpOptions options) throws IOException { - validatePaths(options); - doBuildListing(pathToListFile, options); + DistCpContext distCpContext) throws IOException { + validatePaths(distCpContext); + doBuildListing(pathToListFile, distCpContext); Configuration config = getConf(); config.set(DistCpConstants.CONF_LABEL_LISTING_FILE_PATH, pathToListFile.toString()); config.setLong(DistCpConstants.CONF_LABEL_TOTAL_BYTES_TO_BE_COPIED, getBytesToCopy()); config.setLong(DistCpConstants.CONF_LABEL_TOTAL_NUMBER_OF_RECORDS, getNumberOfPaths()); - validateFinalListing(pathToListFile, options); + validateFinalListing(pathToListFile, distCpContext); LOG.info("Number of paths in the copy list: " + this.getNumberOfPaths()); } /** * Validate input and output paths * - * @param options - Input options + * @param distCpContext - Distcp context * @throws InvalidInputException If inputs are invalid * @throws IOException any Exception with FS */ - protected abstract void validatePaths(DistCpOptions options) + protected abstract void validatePaths(DistCpContext distCpContext) throws IOException, InvalidInputException; /** * The interface to be implemented by sub-classes, to create the source/target file listing. * @param pathToListFile Path on HDFS where the listing file is written. - * @param options Input Options for DistCp (indicating source/target paths.) + * @param distCpContext - Distcp context * @throws IOException Thrown on failure to create the listing file. */ protected abstract void doBuildListing(Path pathToListFile, - DistCpOptions options) throws IOException; + DistCpContext distCpContext) throws IOException; /** * Return the total bytes that distCp should copy for the source paths @@ -135,17 +135,17 @@ public abstract class CopyListing extends Configured { * If preserving XAttrs, checks that file system can support XAttrs. * * @param pathToListFile - path listing build by doBuildListing - * @param options - Input options to distcp + * @param context - Distcp context with associated input options * @throws IOException - Any issues while checking for duplicates and throws * @throws DuplicateFileException - if there are duplicates */ - private void validateFinalListing(Path pathToListFile, DistCpOptions options) + private void validateFinalListing(Path pathToListFile, DistCpContext context) throws DuplicateFileException, IOException { Configuration config = getConf(); FileSystem fs = pathToListFile.getFileSystem(config); - final boolean splitLargeFile = options.splitLargeFile(); + final boolean splitLargeFile = context.splitLargeFile(); // When splitLargeFile is enabled, we don't randomize the copylist // earlier, so we don't do the sorting here. For a file that has @@ -188,7 +188,7 @@ public abstract class CopyListing extends Configured { } } reader.getCurrentValue(lastFileStatus); - if (options.shouldPreserve(DistCpOptions.FileAttribute.ACL)) { + if (context.shouldPreserve(DistCpOptions.FileAttribute.ACL)) { FileSystem lastFs = lastFileStatus.getPath().getFileSystem(config); URI lastFsUri = lastFs.getUri(); if (!aclSupportCheckFsSet.contains(lastFsUri)) { @@ -196,7 +196,7 @@ public abstract class CopyListing extends Configured { aclSupportCheckFsSet.add(lastFsUri); } } - if (options.shouldPreserve(DistCpOptions.FileAttribute.XATTR)) { + if (context.shouldPreserve(DistCpOptions.FileAttribute.XATTR)) { FileSystem lastFs = lastFileStatus.getPath().getFileSystem(config); URI lastFsUri = lastFs.getUri(); if (!xAttrSupportCheckFsSet.contains(lastFsUri)) { @@ -210,7 +210,7 @@ public abstract class CopyListing extends Configured { lastChunkOffset = lastFileStatus.getChunkOffset(); lastChunkLength = lastFileStatus.getChunkLength(); } - if (options.shouldUseDiff() && LOG.isDebugEnabled()) { + if (context.shouldUseDiff() && LOG.isDebugEnabled()) { LOG.debug("Copy list entry " + idx + ": " + lastFileStatus.getPath().toUri().getPath()); idx++; @@ -253,14 +253,12 @@ public abstract class CopyListing extends Configured { * Public Factory method with which the appropriate CopyListing implementation may be retrieved. * @param configuration The input configuration. * @param credentials Credentials object on which the FS delegation tokens are cached - * @param options The input Options, to help choose the appropriate CopyListing Implementation. + * @param context Distcp context with associated input options * @return An instance of the appropriate CopyListing implementation. * @throws java.io.IOException - Exception if any */ public static CopyListing getCopyListing(Configuration configuration, - Credentials credentials, - DistCpOptions options) - throws IOException { + Credentials credentials, DistCpContext context) throws IOException { String copyListingClassName = configuration.get(DistCpConstants. CONF_LABEL_COPY_LISTING_CLASS, ""); Class copyListingClass; @@ -270,7 +268,7 @@ public abstract class CopyListing extends Configured { CONF_LABEL_COPY_LISTING_CLASS, GlobbedCopyListing.class, CopyListing.class); } else { - if (options.getSourceFileListing() == null) { + if (context.getSourceFileListing() == null) { copyListingClass = GlobbedCopyListing.class; } else { copyListingClass = FileBasedCopyListing.class; diff --git a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCp.java b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCp.java index 8c2fa24a15c..df9c32896f3 100644 --- a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCp.java +++ b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCp.java @@ -21,6 +21,7 @@ package org.apache.hadoop.tools; import java.io.IOException; import java.util.Random; +import com.google.common.base.Preconditions; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.classification.InterfaceAudience; @@ -35,7 +36,6 @@ import org.apache.hadoop.mapreduce.Cluster; import org.apache.hadoop.mapreduce.Job; import org.apache.hadoop.mapreduce.JobContext; import org.apache.hadoop.mapreduce.JobSubmissionFiles; -import org.apache.hadoop.tools.DistCpOptions.FileAttribute; import org.apache.hadoop.tools.CopyListing.*; import org.apache.hadoop.tools.mapred.CopyMapper; import org.apache.hadoop.tools.mapred.CopyOutputFormat; @@ -66,7 +66,9 @@ public class DistCp extends Configured implements Tool { static final Log LOG = LogFactory.getLog(DistCp.class); - private DistCpOptions inputOptions; + @VisibleForTesting + DistCpContext context; + private Path metaFolder; private static final String PREFIX = "_distcp"; @@ -79,15 +81,14 @@ public class DistCp extends Configured implements Tool { private FileSystem jobFS; private void prepareFileListing(Job job) throws Exception { - if (inputOptions.shouldUseSnapshotDiff()) { + if (context.shouldUseSnapshotDiff()) { // When "-diff" or "-rdiff" is passed, do sync() first, then // create copyListing based on snapshot diff. - DistCpSync distCpSync = new DistCpSync(inputOptions, getConf()); + DistCpSync distCpSync = new DistCpSync(context, getConf()); if (distCpSync.sync()) { createInputFileListingWithDiff(job, distCpSync); } else { - throw new Exception("DistCp sync failed, input options: " - + inputOptions); + throw new Exception("DistCp sync failed, input options: " + context); } } else { // When no "-diff" or "-rdiff" is passed, create copyListing @@ -99,16 +100,19 @@ public class DistCp extends Configured implements Tool { /** * Public Constructor. Creates DistCp object with specified input-parameters. * (E.g. source-paths, target-location, etc.) - * @param inputOptions Options (indicating source-paths, target-location.) - * @param configuration The Hadoop configuration against which the Copy-mapper must run. + * @param configuration configuration against which the Copy-mapper must run + * @param inputOptions Immutable options * @throws Exception */ - public DistCp(Configuration configuration, DistCpOptions inputOptions) throws Exception { + public DistCp(Configuration configuration, DistCpOptions inputOptions) + throws Exception { Configuration config = new Configuration(configuration); config.addResource(DISTCP_DEFAULT_XML); config.addResource(DISTCP_SITE_XML); setConf(config); - this.inputOptions = inputOptions; + if (inputOptions != null) { + this.context = new DistCpContext(inputOptions); + } this.metaFolder = createMetaFolderPath(); } @@ -134,10 +138,10 @@ public class DistCp extends Configured implements Tool { } try { - inputOptions = (OptionsParser.parse(argv)); - setOptionsForSplitLargeFile(); + context = new DistCpContext(OptionsParser.parse(argv)); + checkSplitLargeFile(); setTargetPathExists(); - LOG.info("Input Options: " + inputOptions); + LOG.info("Input Options: " + context); } catch (Throwable e) { LOG.error("Invalid arguments: ", e); System.err.println("Invalid arguments: " + e.getMessage()); @@ -173,9 +177,11 @@ public class DistCp extends Configured implements Tool { * @throws Exception */ public Job execute() throws Exception { + Preconditions.checkState(context != null, + "The DistCpContext should have been created before running DistCp!"); Job job = createAndSubmitJob(); - if (inputOptions.shouldBlock()) { + if (context.shouldBlock()) { waitForJobCompletion(job); } return job; @@ -186,7 +192,7 @@ public class DistCp extends Configured implements Tool { * @return The mapreduce job object that has been submitted */ public Job createAndSubmitJob() throws Exception { - assert inputOptions != null; + assert context != null; assert getConf() != null; Job job = null; try { @@ -230,53 +236,36 @@ public class DistCp extends Configured implements Tool { * for the benefit of CopyCommitter */ private void setTargetPathExists() throws IOException { - Path target = inputOptions.getTargetPath(); + Path target = context.getTargetPath(); FileSystem targetFS = target.getFileSystem(getConf()); boolean targetExists = targetFS.exists(target); - inputOptions.setTargetPathExists(targetExists); + context.setTargetPathExists(targetExists); getConf().setBoolean(DistCpConstants.CONF_LABEL_TARGET_PATH_EXISTS, targetExists); } /** - * Check if concat is supported by fs. - * Throws UnsupportedOperationException if not. + * Check splitting large files is supported and populate configs. */ - private void checkConcatSupport(FileSystem fs) { + private void checkSplitLargeFile() throws IOException { + if (!context.splitLargeFile()) { + return; + } + + final Path target = context.getTargetPath(); + final FileSystem targetFS = target.getFileSystem(getConf()); try { Path[] src = null; Path tgt = null; - fs.concat(tgt, src); + targetFS.concat(tgt, src); } catch (UnsupportedOperationException use) { throw new UnsupportedOperationException( DistCpOptionSwitch.BLOCKS_PER_CHUNK.getSwitch() + - " is not supported since the target file system doesn't" + - " support concat.", use); + " is not supported since the target file system doesn't" + + " support concat.", use); } catch (Exception e) { // Ignore other exception } - } - - /** - * Set up needed options for splitting large files. - */ - private void setOptionsForSplitLargeFile() throws IOException { - if (!inputOptions.splitLargeFile()) { - return; - } - Path target = inputOptions.getTargetPath(); - FileSystem targetFS = target.getFileSystem(getConf()); - checkConcatSupport(targetFS); - - LOG.info("Enabling preserving blocksize since " - + DistCpOptionSwitch.BLOCKS_PER_CHUNK.getSwitch() + " is passed."); - inputOptions.preserve(FileAttribute.BLOCKSIZE); - - LOG.info("Set " + - DistCpOptionSwitch.APPEND.getSwitch() - + " to false since " + DistCpOptionSwitch.BLOCKS_PER_CHUNK.getSwitch() - + " is passed."); - inputOptions.setAppend(false); LOG.info("Set " + DistCpConstants.CONF_LABEL_SIMPLE_LISTING_RANDOMIZE_FILES @@ -286,7 +275,6 @@ public class DistCp extends Configured implements Tool { DistCpConstants.CONF_LABEL_SIMPLE_LISTING_RANDOMIZE_FILES, false); } - /** * Create Job object for submitting it, with all the configuration * @@ -300,7 +288,7 @@ public class DistCp extends Configured implements Tool { jobName += ": " + userChosenName; Job job = Job.getInstance(getConf()); job.setJobName(jobName); - job.setInputFormatClass(DistCpUtils.getStrategy(getConf(), inputOptions)); + job.setInputFormatClass(DistCpUtils.getStrategy(getConf(), context)); job.setJarByClass(CopyMapper.class); configureOutputFormat(job); @@ -311,9 +299,9 @@ public class DistCp extends Configured implements Tool { job.setOutputFormatClass(CopyOutputFormat.class); job.getConfiguration().set(JobContext.MAP_SPECULATIVE, "false"); job.getConfiguration().set(JobContext.NUM_MAPS, - String.valueOf(inputOptions.getMaxMaps())); + String.valueOf(context.getMaxMaps())); - inputOptions.appendToConf(job.getConfiguration()); + context.appendToConf(job.getConfiguration()); return job; } @@ -325,18 +313,20 @@ public class DistCp extends Configured implements Tool { */ private void configureOutputFormat(Job job) throws IOException { final Configuration configuration = job.getConfiguration(); - Path targetPath = inputOptions.getTargetPath(); + Path targetPath = context.getTargetPath(); FileSystem targetFS = targetPath.getFileSystem(configuration); targetPath = targetPath.makeQualified(targetFS.getUri(), targetFS.getWorkingDirectory()); - if (inputOptions.shouldPreserve(DistCpOptions.FileAttribute.ACL)) { + if (context.shouldPreserve( + DistCpOptions.FileAttribute.ACL)) { DistCpUtils.checkFileSystemAclSupport(targetFS); } - if (inputOptions.shouldPreserve(DistCpOptions.FileAttribute.XATTR)) { + if (context.shouldPreserve( + DistCpOptions.FileAttribute.XATTR)) { DistCpUtils.checkFileSystemXAttrSupport(targetFS); } - if (inputOptions.shouldAtomicCommit()) { - Path workDir = inputOptions.getAtomicWorkPath(); + if (context.shouldAtomicCommit()) { + Path workDir = context.getAtomicWorkPath(); if (workDir == null) { workDir = targetPath.getParent(); } @@ -353,7 +343,7 @@ public class DistCp extends Configured implements Tool { } CopyOutputFormat.setCommitDirectory(job, targetPath); - Path logPath = inputOptions.getLogPath(); + Path logPath = context.getLogPath(); if (logPath == null) { logPath = new Path(metaFolder, "_logs"); } else { @@ -374,8 +364,8 @@ public class DistCp extends Configured implements Tool { protected Path createInputFileListing(Job job) throws IOException { Path fileListingPath = getFileListingPath(); CopyListing copyListing = CopyListing.getCopyListing(job.getConfiguration(), - job.getCredentials(), inputOptions); - copyListing.buildListing(fileListingPath, inputOptions); + job.getCredentials(), context); + copyListing.buildListing(fileListingPath, context); return fileListingPath; } @@ -391,7 +381,7 @@ public class DistCp extends Configured implements Tool { Path fileListingPath = getFileListingPath(); CopyListing copyListing = new SimpleCopyListing(job.getConfiguration(), job.getCredentials(), distCpSync); - copyListing.buildListing(fileListingPath, inputOptions); + copyListing.buildListing(fileListingPath, context); return fileListingPath; } diff --git a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCpContext.java b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCpContext.java new file mode 100644 index 00000000000..c34005e6c4b --- /dev/null +++ b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCpContext.java @@ -0,0 +1,198 @@ +/** + * 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.tools; + +import org.apache.hadoop.classification.InterfaceAudience; +import org.apache.hadoop.classification.InterfaceStability; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.Path; +import org.apache.hadoop.tools.DistCpOptions.FileAttribute; + +import java.util.List; +import java.util.Set; + +/** + * This is the context of the distcp at runtime. + * + * It has the immutable {@link DistCpOptions} and mutable runtime status. + */ +@InterfaceAudience.Private +@InterfaceStability.Evolving +public class DistCpContext { + private final DistCpOptions options; + + /** The source paths can be set at runtime via snapshots. */ + private List sourcePaths; + + /** This is a derived field, it's initialized in the beginning of distcp. */ + private boolean targetPathExists = true; + + /** Indicate that raw.* xattrs should be preserved if true. */ + private boolean preserveRawXattrs = false; + + public DistCpContext(DistCpOptions options) { + this.options = options; + this.sourcePaths = options.getSourcePaths(); + } + + public void setSourcePaths(List sourcePaths) { + this.sourcePaths = sourcePaths; + } + + /** + * @return the sourcePaths. Please note this method does not directly delegate + * to the {@link #options}. + */ + public List getSourcePaths() { + return sourcePaths; + } + + public Path getSourceFileListing() { + return options.getSourceFileListing(); + } + + public Path getTargetPath() { + return options.getTargetPath(); + } + + public boolean shouldAtomicCommit() { + return options.shouldAtomicCommit(); + } + + public boolean shouldSyncFolder() { + return options.shouldSyncFolder(); + } + + public boolean shouldDeleteMissing() { + return options.shouldDeleteMissing(); + } + + public boolean shouldIgnoreFailures() { + return options.shouldIgnoreFailures(); + } + + public boolean shouldOverwrite() { + return options.shouldOverwrite(); + } + + public boolean shouldAppend() { + return options.shouldAppend(); + } + + public boolean shouldSkipCRC() { + return options.shouldSkipCRC(); + } + + public boolean shouldBlock() { + return options.shouldBlock(); + } + + public boolean shouldUseDiff() { + return options.shouldUseDiff(); + } + + public boolean shouldUseRdiff() { + return options.shouldUseRdiff(); + } + + public boolean shouldUseSnapshotDiff() { + return options.shouldUseSnapshotDiff(); + } + + public String getFromSnapshot() { + return options.getFromSnapshot(); + } + + public String getToSnapshot() { + return options.getToSnapshot(); + } + + public final String getFiltersFile() { + return options.getFiltersFile(); + } + + public int getNumListstatusThreads() { + return options.getNumListstatusThreads(); + } + + public int getMaxMaps() { + return options.getMaxMaps(); + } + + public float getMapBandwidth() { + return options.getMapBandwidth(); + } + + public Set getPreserveAttributes() { + return options.getPreserveAttributes(); + } + + public boolean shouldPreserve(FileAttribute attribute) { + return options.shouldPreserve(attribute); + } + + public boolean shouldPreserveRawXattrs() { + return preserveRawXattrs; + } + + public void setPreserveRawXattrs(boolean preserveRawXattrs) { + this.preserveRawXattrs = preserveRawXattrs; + } + + public Path getAtomicWorkPath() { + return options.getAtomicWorkPath(); + } + + public Path getLogPath() { + return options.getLogPath(); + } + + public String getCopyStrategy() { + return options.getCopyStrategy(); + } + + public int getBlocksPerChunk() { + return options.getBlocksPerChunk(); + } + + public final boolean splitLargeFile() { + return options.getBlocksPerChunk() > 0; + } + + public void setTargetPathExists(boolean targetPathExists) { + this.targetPathExists = targetPathExists; + } + + public boolean isTargetPathExists() { + return targetPathExists; + } + + public void appendToConf(Configuration conf) { + options.appendToConf(conf); + } + + @Override + public String toString() { + return options.toString() + + ", sourcePaths=" + sourcePaths + + ", targetPathExists=" + targetPathExists + + ", preserveRawXattrs" + preserveRawXattrs; + } + +} diff --git a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCpOptionSwitch.java b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCpOptionSwitch.java index ced9b540c89..81abb7df13d 100644 --- a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCpOptionSwitch.java +++ b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCpOptionSwitch.java @@ -81,7 +81,7 @@ public enum DistCpOptionSwitch { NUM_LISTSTATUS_THREADS(DistCpConstants.CONF_LABEL_LISTSTATUS_THREADS, new Option("numListstatusThreads", true, "Number of threads to " + "use for building file listing (max " + - DistCpOptions.maxNumListstatusThreads + ").")), + DistCpOptions.MAX_NUM_LISTSTATUS_THREADS + ").")), /** * Max number of maps to use during copy. DistCp will split work * as equally as possible among these maps diff --git a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCpOptions.java b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCpOptions.java index 9822d83dbd0..97ae0c4ef1a 100644 --- a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCpOptions.java +++ b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCpOptions.java @@ -18,43 +18,88 @@ package org.apache.hadoop.tools; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; import org.apache.hadoop.tools.util.DistCpUtils; +import java.util.Collections; import java.util.EnumSet; -import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; +import java.util.Set; /** * The Options class encapsulates all DistCp options. - * These may be set from command-line (via the OptionsParser) - * or may be set manually. + * + * When you add a new option, please: + * - Add the field along with javadoc in DistCpOptions and its Builder + * - Add setter method in the {@link Builder} class + * + * This class is immutable. */ -public class DistCpOptions { +public final class DistCpOptions { + private static final Logger LOG = LoggerFactory.getLogger(Builder.class); + public static final int MAX_NUM_LISTSTATUS_THREADS = 40; + + /** File path (hdfs:// or file://) that contains the list of actual files to + * copy. + */ + private final Path sourceFileListing; + + /** List of source-paths (including wildcards) to be copied to target. */ + private final List sourcePaths; + + /** Destination path for the dist-copy. */ + private final Path targetPath; + + /** Whether data need to be committed automatically. */ + private final boolean atomicCommit; + + /** the work path for atomic commit. If null, the work + * path would be parentOf(targetPath) + "/._WIP_" + nameOf(targetPath). */ + private final Path atomicWorkPath; + + /** Whether source and target folder contents be sync'ed up. */ + private final boolean syncFolder; + + /** Whether files only present in target should be deleted. */ + private boolean deleteMissing; + + /** Whether failures during copy be ignored. */ + private final boolean ignoreFailures; + + /** Whether files should always be overwritten on target. */ + private final boolean overwrite; + + /** Whether we want to append new data to target files. This is valid only + * with update option and CRC is not skipped. */ + private final boolean append; + + /** Whether checksum comparison should be skipped while determining if source + * and destination files are identical. */ + private final boolean skipCRC; + + /** Whether to run blocking or non-blocking. */ + private final boolean blocking; - private boolean atomicCommit = false; - private boolean syncFolder = false; - private boolean deleteMissing = false; - private boolean ignoreFailures = false; - private boolean overwrite = false; - private boolean append = false; - private boolean skipCRC = false; - private boolean blocking = true; // When "-diff s1 s2 src tgt" is passed, apply forward snapshot diff (from s1 // to s2) of source cluster to the target cluster to sync target cluster with // the source cluster. Referred to as "Fdiff" in the code. // It's required that s2 is newer than s1. - private boolean useDiff = false; + private final boolean useDiff; // When "-rdiff s2 s1 src tgt" is passed, apply reversed snapshot diff (from // s2 to s1) of target cluster to the target cluster, so to make target // cluster go back to s1. Referred to as "Rdiff" in the code. // It's required that s2 is newer than s1, and src and tgt have exact same // content at their s1, if src is not the same as tgt. - private boolean useRdiff = false; + private final boolean useRdiff; // For both -diff and -rdiff, given the example command line switches, two // steps are taken: @@ -66,44 +111,53 @@ public class DistCpOptions { // could be the tgt itself (HDFS-9820). // - public static final int maxNumListstatusThreads = 40; - private int numListstatusThreads = 0; // Indicates that flag is not set. - private int maxMaps = DistCpConstants.DEFAULT_MAPS; - private float mapBandwidth = 0; // Indicates that we should use the default. + private final String fromSnapshot; + private final String toSnapshot; - private String copyStrategy = DistCpConstants.UNIFORMSIZE; + /** The path to a file containing a list of paths to filter out of copy. */ + private final String filtersFile; - private EnumSet preserveStatus = EnumSet.noneOf(FileAttribute.class); + /** Path where output logs are stored. If not specified, it will use the + * default value JobStagingDir/_logs and delete upon job completion. */ + private final Path logPath; - private boolean preserveRawXattrs; + /** Set the copy strategy to use. Should map to a strategy implementation + * in distp-default.xml. */ + private final String copyStrategy; - private Path atomicWorkPath; + /** per map bandwidth in MB. */ + private final float mapBandwidth; - private Path logPath; + /** The number of threads to use for listStatus. We allow max + * {@link #MAX_NUM_LISTSTATUS_THREADS} threads. Setting numThreads to zero + * signify we should use the value from conf properties. */ + private final int numListstatusThreads; - private Path sourceFileListing; - private List sourcePaths; + /** The max number of maps to use for copy. */ + private final int maxMaps; - private String fromSnapshot; - private String toSnapshot; - - private Path targetPath; - - /** - * The path to a file containing a list of paths to filter out of the copy. - */ - private String filtersFile; - - // targetPathExist is a derived field, it's initialized in the - // beginning of distcp. - private boolean targetPathExists = true; + /** File attributes that need to be preserved. */ + private final EnumSet preserveStatus; // Size of chunk in number of blocks when splitting large file into chunks // to copy in parallel. Default is 0 and file are not splitted. - private int blocksPerChunk = 0; + private final int blocksPerChunk; - public static enum FileAttribute{ - REPLICATION, BLOCKSIZE, USER, GROUP, PERMISSION, CHECKSUMTYPE, ACL, XATTR, TIMES; + /** + * File attributes for preserve. + * + * Each enum entry uses the first char as its symbol. + */ + public enum FileAttribute { + REPLICATION, // R + BLOCKSIZE, // B + USER, // U + GROUP, // G + PERMISSION, // P + CHECKSUMTYPE, // C + ACL, // A + XATTR, // X + TIMES; // T public static FileAttribute getAttribute(char symbol) { for (FileAttribute attribute : values()) { @@ -115,187 +169,86 @@ public class DistCpOptions { } } - /** - * Constructor, to initialize source/target paths. - * @param sourcePaths List of source-paths (including wildcards) - * to be copied to target. - * @param targetPath Destination path for the dist-copy. - */ - public DistCpOptions(List sourcePaths, Path targetPath) { - assert sourcePaths != null && !sourcePaths.isEmpty() : "Invalid source paths"; - assert targetPath != null : "Invalid Target path"; + private DistCpOptions(Builder builder) { + this.sourceFileListing = builder.sourceFileListing; + this.sourcePaths = builder.sourcePaths; + this.targetPath = builder.targetPath; - this.sourcePaths = sourcePaths; - this.targetPath = targetPath; + this.atomicCommit = builder.atomicCommit; + this.atomicWorkPath = builder.atomicWorkPath; + this.syncFolder = builder.syncFolder; + this.deleteMissing = builder.deleteMissing; + this.ignoreFailures = builder.ignoreFailures; + this.overwrite = builder.overwrite; + this.append = builder.append; + this.skipCRC = builder.skipCRC; + this.blocking = builder.blocking; + + this.useDiff = builder.useDiff; + this.useRdiff = builder.useRdiff; + this.fromSnapshot = builder.fromSnapshot; + this.toSnapshot = builder.toSnapshot; + + this.filtersFile = builder.filtersFile; + this.logPath = builder.logPath; + this.copyStrategy = builder.copyStrategy; + + this.mapBandwidth = builder.mapBandwidth; + this.numListstatusThreads = builder.numListstatusThreads; + this.maxMaps = builder.maxMaps; + + this.preserveStatus = builder.preserveStatus; + + this.blocksPerChunk = builder.blocksPerChunk; } - /** - * Constructor, to initialize source/target paths. - * @param sourceFileListing File containing list of source paths - * @param targetPath Destination path for the dist-copy. - */ - public DistCpOptions(Path sourceFileListing, Path targetPath) { - assert sourceFileListing != null : "Invalid source paths"; - assert targetPath != null : "Invalid Target path"; - - this.sourceFileListing = sourceFileListing; - this.targetPath = targetPath; + public Path getSourceFileListing() { + return sourceFileListing; } - /** - * Copy constructor. - * @param that DistCpOptions being copied from. - */ - public DistCpOptions(DistCpOptions that) { - if (this != that && that != null) { - this.atomicCommit = that.atomicCommit; - this.syncFolder = that.syncFolder; - this.deleteMissing = that.deleteMissing; - this.ignoreFailures = that.ignoreFailures; - this.overwrite = that.overwrite; - this.skipCRC = that.skipCRC; - this.blocking = that.blocking; - this.useDiff = that.useDiff; - this.useRdiff = that.useRdiff; - this.numListstatusThreads = that.numListstatusThreads; - this.maxMaps = that.maxMaps; - this.mapBandwidth = that.mapBandwidth; - this.copyStrategy = that.copyStrategy; - this.preserveStatus = that.preserveStatus; - this.preserveRawXattrs = that.preserveRawXattrs; - this.atomicWorkPath = that.getAtomicWorkPath(); - this.logPath = that.getLogPath(); - this.sourceFileListing = that.getSourceFileListing(); - this.sourcePaths = that.getSourcePaths(); - this.targetPath = that.getTargetPath(); - this.targetPathExists = that.getTargetPathExists(); - this.filtersFile = that.getFiltersFile(); - this.blocksPerChunk = that.blocksPerChunk; - } + public List getSourcePaths() { + return sourcePaths == null ? + null : Collections.unmodifiableList(sourcePaths); + } + + public Path getTargetPath() { + return targetPath; } - /** - * Should the data be committed atomically? - * - * @return true if data should be committed automically. false otherwise - */ public boolean shouldAtomicCommit() { return atomicCommit; } - /** - * Set if data need to be committed automatically - * - * @param atomicCommit - boolean switch - */ - public void setAtomicCommit(boolean atomicCommit) { - this.atomicCommit = atomicCommit; + public Path getAtomicWorkPath() { + return atomicWorkPath; } - /** - * Should the data be sync'ed between source and target paths? - * - * @return true if data should be sync'ed up. false otherwise - */ public boolean shouldSyncFolder() { return syncFolder; } - /** - * Set if source and target folder contents be sync'ed up - * - * @param syncFolder - boolean switch - */ - public void setSyncFolder(boolean syncFolder) { - this.syncFolder = syncFolder; - } - - /** - * Should target files missing in source should be deleted? - * - * @return true if zoombie target files to be removed. false otherwise - */ public boolean shouldDeleteMissing() { return deleteMissing; } - /** - * Set if files only present in target should be deleted - * - * @param deleteMissing - boolean switch - */ - public void setDeleteMissing(boolean deleteMissing) { - this.deleteMissing = deleteMissing; - } - - /** - * Should failures be logged and ignored during copy? - * - * @return true if failures are to be logged and ignored. false otherwise - */ public boolean shouldIgnoreFailures() { return ignoreFailures; } - /** - * Set if failures during copy be ignored - * - * @param ignoreFailures - boolean switch - */ - public void setIgnoreFailures(boolean ignoreFailures) { - this.ignoreFailures = ignoreFailures; - } - - /** - * Should DistCp be running in blocking mode - * - * @return true if should run in blocking, false otherwise - */ - public boolean shouldBlock() { - return blocking; - } - - /** - * Set if Disctp should run blocking or non-blocking - * - * @param blocking - boolean switch - */ - public void setBlocking(boolean blocking) { - this.blocking = blocking; - } - - /** - * Should files be overwritten always? - * - * @return true if files in target that may exist before distcp, should always - * be overwritten. false otherwise - */ public boolean shouldOverwrite() { return overwrite; } - /** - * Set if files should always be overwritten on target - * - * @param overwrite - boolean switch - */ - public void setOverwrite(boolean overwrite) { - this.overwrite = overwrite; - } - - /** - * @return whether we can append new data to target files - */ public boolean shouldAppend() { return append; } - /** - * Set if we want to append new data to target files. This is valid only with - * update option and CRC is not skipped. - */ - public void setAppend(boolean append) { - this.append = append; + public boolean shouldSkipCRC() { + return skipCRC; + } + + public boolean shouldBlock() { + return blocking; } public boolean shouldUseDiff() { @@ -318,104 +271,34 @@ public class DistCpOptions { return this.toSnapshot; } - public void setUseDiff(String fromSS, String toSS) { - this.useDiff = true; - this.fromSnapshot = fromSS; - this.toSnapshot = toSS; + public String getFiltersFile() { + return filtersFile; } - public void setUseRdiff(String fromSS, String toSS) { - this.useRdiff = true; - this.fromSnapshot = fromSS; - this.toSnapshot = toSS; + public Path getLogPath() { + return logPath; } - /** - * Should CRC/checksum check be skipped while checking files are identical - * - * @return true if checksum check should be skipped while checking files are - * identical. false otherwise - */ - public boolean shouldSkipCRC() { - return skipCRC; + public String getCopyStrategy() { + return copyStrategy; } - /** - * Set if checksum comparison should be skipped while determining if - * source and destination files are identical - * - * @param skipCRC - boolean switch - */ - public void setSkipCRC(boolean skipCRC) { - this.skipCRC = skipCRC; - } - - /** Get the number of threads to use for listStatus - * - * @return Number of threads to do listStatus - */ public int getNumListstatusThreads() { return numListstatusThreads; } - /** Set the number of threads to use for listStatus. We allow max 40 - * threads. Setting numThreads to zero signify we should use the value - * from conf properties. - * - * @param numThreads - Number of threads - */ - public void setNumListstatusThreads(int numThreads) { - if (numThreads > maxNumListstatusThreads) { - this.numListstatusThreads = maxNumListstatusThreads; - } else if (numThreads > 0) { - this.numListstatusThreads = numThreads; - } else { - this.numListstatusThreads = 0; - } - } - - /** Get the max number of maps to use for this copy - * - * @return Max number of maps - */ public int getMaxMaps() { return maxMaps; } - /** - * Set the max number of maps to use for copy - * - * @param maxMaps - Number of maps - */ - public void setMaxMaps(int maxMaps) { - this.maxMaps = Math.max(maxMaps, 1); - } - - /** Get the map bandwidth in MB - * - * @return Bandwidth in MB - */ public float getMapBandwidth() { return mapBandwidth; } - /** - * Set per map bandwidth - * - * @param mapBandwidth - per map bandwidth - */ - public void setMapBandwidth(float mapBandwidth) { - assert mapBandwidth > 0 : "Bandwidth " + mapBandwidth + " is invalid (should be > 0)"; - this.mapBandwidth = mapBandwidth; - } - - /** - * Returns an iterator with the list of file attributes to preserve - * - * @return iterator of file attributes to preserve - */ - public Iterator preserveAttributes() { - return preserveStatus.iterator(); + public Set getPreserveAttributes() { + return (preserveStatus == null) + ? null + : Collections.unmodifiableSet(preserveStatus); } /** @@ -428,230 +311,10 @@ public class DistCpOptions { return preserveStatus.contains(attribute); } - /** - * Add file attributes that need to be preserved. This method may be - * called multiple times to add attributes. - * - * @param fileAttribute - Attribute to add, one at a time - */ - public void preserve(FileAttribute fileAttribute) { - for (FileAttribute attribute : preserveStatus) { - if (attribute.equals(fileAttribute)) { - return; - } - } - preserveStatus.add(fileAttribute); - } - - /** - * Return true if raw.* xattrs should be preserved. - * @return true if raw.* xattrs should be preserved. - */ - public boolean shouldPreserveRawXattrs() { - return preserveRawXattrs; - } - - /** - * Indicate that raw.* xattrs should be preserved - */ - public void preserveRawXattrs() { - preserveRawXattrs = true; - } - - /** Get work path for atomic commit. If null, the work - * path would be parentOf(targetPath) + "/._WIP_" + nameOf(targetPath) - * - * @return Atomic work path on the target cluster. Null if not set - */ - public Path getAtomicWorkPath() { - return atomicWorkPath; - } - - /** - * Set the work path for atomic commit - * - * @param atomicWorkPath - Path on the target cluster - */ - public void setAtomicWorkPath(Path atomicWorkPath) { - this.atomicWorkPath = atomicWorkPath; - } - - /** Get output directory for writing distcp logs. Otherwise logs - * are temporarily written to JobStagingDir/_logs and deleted - * upon job completion - * - * @return Log output path on the cluster where distcp job is run - */ - public Path getLogPath() { - return logPath; - } - - /** - * Set the log path where distcp output logs are stored - * Uses JobStagingDir/_logs by default - * - * @param logPath - Path where logs will be saved - */ - public void setLogPath(Path logPath) { - this.logPath = logPath; - } - - /** - * Get the copy strategy to use. Uses appropriate input format - * - * @return copy strategy to use - */ - public String getCopyStrategy() { - return copyStrategy; - } - - /** - * Set the copy strategy to use. Should map to a strategy implementation - * in distp-default.xml - * - * @param copyStrategy - copy Strategy to use - */ - public void setCopyStrategy(String copyStrategy) { - this.copyStrategy = copyStrategy; - } - - /** - * File path (hdfs:// or file://) that contains the list of actual - * files to copy - * - * @return - Source listing file path - */ - public Path getSourceFileListing() { - return sourceFileListing; - } - - /** - * Getter for sourcePaths. - * @return List of source-paths. - */ - public List getSourcePaths() { - return sourcePaths; - } - - /** - * Setter for sourcePaths. - * @param sourcePaths The new list of source-paths. - */ - public void setSourcePaths(List sourcePaths) { - assert sourcePaths != null && sourcePaths.size() != 0; - this.sourcePaths = sourcePaths; - } - - /** - * Getter for the targetPath. - * @return The target-path. - */ - public Path getTargetPath() { - return targetPath; - } - - /** - * Getter for the targetPathExists. - * @return The target-path. - */ - public boolean getTargetPathExists() { - return targetPathExists; - } - - /** - * Set targetPathExists. - * @param targetPathExists Whether the target path of distcp exists. - */ - public boolean setTargetPathExists(boolean targetPathExists) { - return this.targetPathExists = targetPathExists; - } - - /** - * File path that contains the list of patterns - * for paths to be filtered from the file copy. - * @return - Filter file path. - */ - public final String getFiltersFile() { - return filtersFile; - } - - /** - * Set filtersFile. - * @param filtersFilename The path to a list of patterns to exclude from copy. - */ - public final void setFiltersFile(String filtersFilename) { - this.filtersFile = filtersFilename; - } - - public final void setBlocksPerChunk(int csize) { - this.blocksPerChunk = csize; - } - - public final int getBlocksPerChunk() { + public int getBlocksPerChunk() { return blocksPerChunk; } - public final boolean splitLargeFile() { - return blocksPerChunk > 0; - } - - void validate() { - if ((useDiff || useRdiff) && deleteMissing) { - // -delete and -diff/-rdiff are mutually exclusive. For backward - // compatibility, we ignore the -delete option here, instead of throwing - // an IllegalArgumentException. See HDFS-10397 for more discussion. - OptionsParser.LOG.warn( - "-delete and -diff/-rdiff are mutually exclusive. " + - "The -delete option will be ignored."); - setDeleteMissing(false); - } - - if (syncFolder && atomicCommit) { - throw new IllegalArgumentException("Atomic commit can't be used with " + - "sync folder or overwrite options"); - } - - if (deleteMissing && !(overwrite || syncFolder)) { - throw new IllegalArgumentException("Delete missing is applicable " + - "only with update or overwrite options"); - } - - if (overwrite && syncFolder) { - throw new IllegalArgumentException("Overwrite and update options are " + - "mutually exclusive"); - } - - if (!syncFolder && skipCRC) { - throw new IllegalArgumentException("Skip CRC is valid only with update options"); - } - - if (!syncFolder && append) { - throw new IllegalArgumentException( - "Append is valid only with update options"); - } - if (skipCRC && append) { - throw new IllegalArgumentException( - "Append is disallowed when skipping CRC"); - } - if (!syncFolder && (useDiff || useRdiff)) { - throw new IllegalArgumentException( - "-diff/-rdiff is valid only with -update option"); - } - - if (useDiff || useRdiff) { - if (StringUtils.isBlank(fromSnapshot) || - StringUtils.isBlank(toSnapshot)) { - throw new IllegalArgumentException( - "Must provide both the starting and ending " + - "snapshot names for -diff/-rdiff"); - } - } - if (useDiff && useRdiff) { - throw new IllegalArgumentException( - "-diff and -rdiff are mutually exclusive"); - } - } - /** * Add options to configuration. These will be used in the Mapper/committer * @@ -715,20 +378,292 @@ public class DistCpOptions { ", mapBandwidth=" + mapBandwidth + ", copyStrategy='" + copyStrategy + '\'' + ", preserveStatus=" + preserveStatus + - ", preserveRawXattrs=" + preserveRawXattrs + ", atomicWorkPath=" + atomicWorkPath + ", logPath=" + logPath + ", sourceFileListing=" + sourceFileListing + ", sourcePaths=" + sourcePaths + ", targetPath=" + targetPath + - ", targetPathExists=" + targetPathExists + ", filtersFile='" + filtersFile + '\'' + ", blocksPerChunk=" + blocksPerChunk + '}'; } - @Override - protected DistCpOptions clone() throws CloneNotSupportedException { - return (DistCpOptions) super.clone(); + /** + * The builder of the {@link DistCpOptions}. + * + * This is designed to be the only public interface to create a + * {@link DistCpOptions} object for users. It follows a simple Builder design + * pattern. + */ + public static class Builder { + private Path sourceFileListing; + private List sourcePaths; + private Path targetPath; + + private boolean atomicCommit = false; + private Path atomicWorkPath; + private boolean syncFolder = false; + private boolean deleteMissing = false; + private boolean ignoreFailures = false; + private boolean overwrite = false; + private boolean append = false; + private boolean skipCRC = false; + private boolean blocking = true; + + private boolean useDiff = false; + private boolean useRdiff = false; + private String fromSnapshot; + private String toSnapshot; + + private String filtersFile; + + private Path logPath; + private String copyStrategy = DistCpConstants.UNIFORMSIZE; + + private int numListstatusThreads = 0; // 0 indicates that flag is not set. + private int maxMaps = DistCpConstants.DEFAULT_MAPS; + private float mapBandwidth = 0; // 0 indicates we should use the default + + private EnumSet preserveStatus = + EnumSet.noneOf(FileAttribute.class); + + private int blocksPerChunk = 0; + + public Builder(List sourcePaths, Path targetPath) { + Preconditions.checkArgument(sourcePaths != null && !sourcePaths.isEmpty(), + "Source paths should not be null or empty!"); + Preconditions.checkArgument(targetPath != null, + "Target path should not be null!"); + this.sourcePaths = sourcePaths; + this.targetPath = targetPath; + } + + public Builder(Path sourceFileListing, Path targetPath) { + Preconditions.checkArgument(sourceFileListing != null, + "Source file listing should not be null!"); + Preconditions.checkArgument(targetPath != null, + "Target path should not be null!"); + + this.sourceFileListing = sourceFileListing; + this.targetPath = targetPath; + } + + /** + * This is the single entry point for constructing DistCpOptions objects. + * + * Before a new DistCpOptions object is returned, it will set the dependent + * options, validate the option combinations. After constructing, the + * DistCpOptions instance is immutable. + */ + public DistCpOptions build() { + setOptionsForSplitLargeFile(); + + validate(); + + return new DistCpOptions(this); + } + + /** + * Override options for split large files. + */ + private void setOptionsForSplitLargeFile() { + if (blocksPerChunk <= 0) { + return; + } + + LOG.info("Enabling preserving blocksize since " + + DistCpOptionSwitch.BLOCKS_PER_CHUNK.getSwitch() + " is passed."); + preserve(FileAttribute.BLOCKSIZE); + + LOG.info("Set " + DistCpOptionSwitch.APPEND.getSwitch() + + " to false since " + DistCpOptionSwitch.BLOCKS_PER_CHUNK.getSwitch() + + " is passed."); + this.append = false; + } + + private void validate() { + if ((useDiff || useRdiff) && deleteMissing) { + // -delete and -diff/-rdiff are mutually exclusive. + throw new IllegalArgumentException("-delete and -diff/-rdiff are " + + "mutually exclusive. The -delete option will be ignored."); + } + + if (!atomicCommit && atomicWorkPath != null) { + throw new IllegalArgumentException( + "-tmp work-path can only be specified along with -atomic"); + } + + if (syncFolder && atomicCommit) { + throw new IllegalArgumentException("Atomic commit can't be used with " + + "sync folder or overwrite options"); + } + + if (deleteMissing && !(overwrite || syncFolder)) { + throw new IllegalArgumentException("Delete missing is applicable " + + "only with update or overwrite options"); + } + + if (overwrite && syncFolder) { + throw new IllegalArgumentException("Overwrite and update options are " + + "mutually exclusive"); + } + + if (!syncFolder && skipCRC) { + throw new IllegalArgumentException( + "Skip CRC is valid only with update options"); + } + + if (!syncFolder && append) { + throw new IllegalArgumentException( + "Append is valid only with update options"); + } + if (skipCRC && append) { + throw new IllegalArgumentException( + "Append is disallowed when skipping CRC"); + } + if (!syncFolder && (useDiff || useRdiff)) { + throw new IllegalArgumentException( + "-diff/-rdiff is valid only with -update option"); + } + + if (useDiff || useRdiff) { + if (StringUtils.isBlank(fromSnapshot) || + StringUtils.isBlank(toSnapshot)) { + throw new IllegalArgumentException( + "Must provide both the starting and ending " + + "snapshot names for -diff/-rdiff"); + } + } + if (useDiff && useRdiff) { + throw new IllegalArgumentException( + "-diff and -rdiff are mutually exclusive"); + } + } + + @VisibleForTesting + Builder withSourcePaths(List newSourcePaths) { + this.sourcePaths = newSourcePaths; + return this; + } + + public Builder withAtomicCommit(boolean newAtomicCommit) { + this.atomicCommit = newAtomicCommit; + return this; + } + + public Builder withAtomicWorkPath(Path newAtomicWorkPath) { + this.atomicWorkPath = newAtomicWorkPath; + return this; + } + + public Builder withSyncFolder(boolean newSyncFolder) { + this.syncFolder = newSyncFolder; + return this; + } + + public Builder withDeleteMissing(boolean newDeleteMissing) { + this.deleteMissing = newDeleteMissing; + return this; + } + + public Builder withIgnoreFailures(boolean newIgnoreFailures) { + this.ignoreFailures = newIgnoreFailures; + return this; + } + + public Builder withOverwrite(boolean newOverwrite) { + this.overwrite = newOverwrite; + return this; + } + + public Builder withAppend(boolean newAppend) { + this.append = newAppend; + return this; + } + + public Builder withCRC(boolean newSkipCRC) { + this.skipCRC = newSkipCRC; + return this; + } + + public Builder withBlocking(boolean newBlocking) { + this.blocking = newBlocking; + return this; + } + + public Builder withUseDiff(String newFromSnapshot, String newToSnapshot) { + this.useDiff = true; + this.fromSnapshot = newFromSnapshot; + this.toSnapshot = newToSnapshot; + return this; + } + + public Builder withUseRdiff(String newFromSnapshot, String newToSnapshot) { + this.useRdiff = true; + this.fromSnapshot = newFromSnapshot; + this.toSnapshot = newToSnapshot; + return this; + } + + public Builder withFiltersFile(String newFiletersFile) { + this.filtersFile = newFiletersFile; + return this; + } + + public Builder withLogPath(Path newLogPath) { + this.logPath = newLogPath; + return this; + } + + public Builder withCopyStrategy(String newCopyStrategy) { + this.copyStrategy = newCopyStrategy; + return this; + } + + public Builder withMapBandwidth(float newMapBandwidth) { + Preconditions.checkArgument(newMapBandwidth > 0, + "Bandwidth " + newMapBandwidth + " is invalid (should be > 0)"); + this.mapBandwidth = newMapBandwidth; + return this; + } + + public Builder withNumListstatusThreads(int newNumListstatusThreads) { + if (newNumListstatusThreads > MAX_NUM_LISTSTATUS_THREADS) { + this.numListstatusThreads = MAX_NUM_LISTSTATUS_THREADS; + } else if (newNumListstatusThreads > 0) { + this.numListstatusThreads = newNumListstatusThreads; + } else { + this.numListstatusThreads = 0; + } + return this; + } + + public Builder maxMaps(int newMaxMaps) { + this.maxMaps = Math.max(newMaxMaps, 1); + return this; + } + + public Builder preserve(String attributes) { + if (attributes == null || attributes.isEmpty()) { + preserveStatus = EnumSet.allOf(FileAttribute.class); + } else { + for (int index = 0; index < attributes.length(); index++) { + preserveStatus.add(FileAttribute. + getAttribute(attributes.charAt(index))); + } + } + return this; + } + + public Builder preserve(FileAttribute attribute) { + preserveStatus.add(attribute); + return this; + } + + public Builder withBlocksPerChunk(int newBlocksPerChunk) { + this.blocksPerChunk = newBlocksPerChunk; + return this; + } } + } diff --git a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCpSync.java b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCpSync.java index bcae96a8d83..a78320b05bf 100644 --- a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCpSync.java +++ b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/DistCpSync.java @@ -48,7 +48,7 @@ import java.util.HashSet; * source.s1 */ class DistCpSync { - private DistCpOptions inputOptions; + private DistCpContext context; private Configuration conf; // diffMap maps snapshot diff op type to a list of diff ops. // It's initially created based on the snapshot diff. Then the individual @@ -58,13 +58,13 @@ class DistCpSync { private EnumMap> diffMap; private DiffInfo[] renameDiffs; - DistCpSync(DistCpOptions options, Configuration conf) { - this.inputOptions = options; + DistCpSync(DistCpContext context, Configuration conf) { + this.context = context; this.conf = conf; } private boolean isRdiff() { - return inputOptions.shouldUseRdiff(); + return context.shouldUseRdiff(); } /** @@ -77,14 +77,14 @@ class DistCpSync { * default distcp if the third condition isn't met. */ private boolean preSyncCheck() throws IOException { - List sourcePaths = inputOptions.getSourcePaths(); + List sourcePaths = context.getSourcePaths(); if (sourcePaths.size() != 1) { // we only support one source dir which must be a snapshottable directory throw new IllegalArgumentException(sourcePaths.size() + " source paths are provided"); } final Path sourceDir = sourcePaths.get(0); - final Path targetDir = inputOptions.getTargetPath(); + final Path targetDir = context.getTargetPath(); final FileSystem srcFs = sourceDir.getFileSystem(conf); final FileSystem tgtFs = targetDir.getFileSystem(conf); @@ -104,13 +104,15 @@ class DistCpSync { // make sure targetFS has no change between from and the current states if (!checkNoChange(targetFs, targetDir)) { // set the source path using the snapshot path - inputOptions.setSourcePaths(Arrays.asList(getSnapshotPath(sourceDir, - inputOptions.getToSnapshot()))); + context.setSourcePaths(Arrays.asList(getSnapshotPath(sourceDir, + context.getToSnapshot()))); return false; } - final String from = getSnapshotName(inputOptions.getFromSnapshot()); - final String to = getSnapshotName(inputOptions.getToSnapshot()); + final String from = getSnapshotName( + context.getFromSnapshot()); + final String to = getSnapshotName( + context.getToSnapshot()); try { final FileStatus fromSnapshotStat = @@ -152,9 +154,9 @@ class DistCpSync { return false; } - List sourcePaths = inputOptions.getSourcePaths(); + List sourcePaths = context.getSourcePaths(); final Path sourceDir = sourcePaths.get(0); - final Path targetDir = inputOptions.getTargetPath(); + final Path targetDir = context.getTargetPath(); final FileSystem tfs = targetDir.getFileSystem(conf); final DistributedFileSystem targetFs = (DistributedFileSystem) tfs; @@ -175,8 +177,8 @@ class DistCpSync { deleteTargetTmpDir(targetFs, tmpDir); // TODO: since we have tmp directory, we can support "undo" with failures // set the source path using the snapshot path - inputOptions.setSourcePaths(Arrays.asList(getSnapshotPath(sourceDir, - inputOptions.getToSnapshot()))); + context.setSourcePaths(Arrays.asList(getSnapshotPath(sourceDir, + context.getToSnapshot()))); } } @@ -187,13 +189,13 @@ class DistCpSync { */ private boolean getAllDiffs() throws IOException { Path ssDir = isRdiff()? - inputOptions.getTargetPath() : inputOptions.getSourcePaths().get(0); + context.getTargetPath() : context.getSourcePaths().get(0); try { DistributedFileSystem fs = (DistributedFileSystem) ssDir.getFileSystem(conf); - final String from = getSnapshotName(inputOptions.getFromSnapshot()); - final String to = getSnapshotName(inputOptions.getToSnapshot()); + final String from = getSnapshotName(context.getFromSnapshot()); + final String to = getSnapshotName(context.getToSnapshot()); SnapshotDiffReport report = fs.getSnapshotDiffReport(ssDir, from, to); this.diffMap = new EnumMap<>(SnapshotDiffReport.DiffType.class); @@ -273,19 +275,19 @@ class DistCpSync { */ private boolean checkNoChange(DistributedFileSystem fs, Path path) { try { - final String from = getSnapshotName(inputOptions.getFromSnapshot()); + final String from = getSnapshotName(context.getFromSnapshot()); SnapshotDiffReport targetDiff = fs.getSnapshotDiffReport(path, from, ""); if (!targetDiff.getDiffList().isEmpty()) { DistCp.LOG.warn("The target has been modified since snapshot " - + inputOptions.getFromSnapshot()); + + context.getFromSnapshot()); return false; } else { return true; } } catch (IOException e) { DistCp.LOG.warn("Failed to compute snapshot diff on " + path - + " at snapshot " + inputOptions.getFromSnapshot(), e); + + " at snapshot " + context.getFromSnapshot(), e); } return false; } diff --git a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/FileBasedCopyListing.java b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/FileBasedCopyListing.java index 2bc343e1727..c356edd4251 100644 --- a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/FileBasedCopyListing.java +++ b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/FileBasedCopyListing.java @@ -52,7 +52,7 @@ public class FileBasedCopyListing extends CopyListing { /** {@inheritDoc} */ @Override - protected void validatePaths(DistCpOptions options) + protected void validatePaths(DistCpContext context) throws IOException, InvalidInputException { } @@ -60,14 +60,14 @@ public class FileBasedCopyListing extends CopyListing { * Implementation of CopyListing::buildListing(). * Iterates over all source paths mentioned in the input-file. * @param pathToListFile Path on HDFS where the listing file is written. - * @param options Input Options for DistCp (indicating source/target paths.) + * @param context Distcp context with associated input options. * @throws IOException */ @Override - public void doBuildListing(Path pathToListFile, DistCpOptions options) throws IOException { - DistCpOptions newOption = new DistCpOptions(options); - newOption.setSourcePaths(fetchFileList(options.getSourceFileListing())); - globbedListing.buildListing(pathToListFile, newOption); + public void doBuildListing(Path pathToListFile, DistCpContext context) + throws IOException { + context.setSourcePaths(fetchFileList(context.getSourceFileListing())); + globbedListing.buildListing(pathToListFile, context); } private List fetchFileList(Path sourceListing) throws IOException { diff --git a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/GlobbedCopyListing.java b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/GlobbedCopyListing.java index 27330b78f72..63c6f436e59 100644 --- a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/GlobbedCopyListing.java +++ b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/GlobbedCopyListing.java @@ -51,7 +51,7 @@ public class GlobbedCopyListing extends CopyListing { /** {@inheritDoc} */ @Override - protected void validatePaths(DistCpOptions options) + protected void validatePaths(DistCpContext context) throws IOException, InvalidInputException { } @@ -60,19 +60,19 @@ public class GlobbedCopyListing extends CopyListing { * Creates the copy listing by "globbing" all source-paths. * @param pathToListingFile The location at which the copy-listing file * is to be created. - * @param options Input Options for DistCp (indicating source/target paths.) + * @param context The distcp context with associated input options. * @throws IOException */ @Override - public void doBuildListing(Path pathToListingFile, - DistCpOptions options) throws IOException { + public void doBuildListing(Path pathToListingFile, DistCpContext context) + throws IOException { List globbedPaths = new ArrayList(); - if (options.getSourcePaths().isEmpty()) { + if (context.getSourcePaths().isEmpty()) { throw new InvalidInputException("Nothing to process. Source paths::EMPTY"); } - for (Path p : options.getSourcePaths()) { + for (Path p : context.getSourcePaths()) { FileSystem fs = p.getFileSystem(getConf()); FileStatus[] inputs = fs.globStatus(p); @@ -85,9 +85,8 @@ public class GlobbedCopyListing extends CopyListing { } } - DistCpOptions optionsGlobbed = new DistCpOptions(options); - optionsGlobbed.setSourcePaths(globbedPaths); - simpleListing.buildListing(pathToListingFile, optionsGlobbed); + context.setSourcePaths(globbedPaths); + simpleListing.buildListing(pathToListingFile, context); } /** {@inheritDoc} */ diff --git a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/OptionsParser.java b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/OptionsParser.java index 8881264d281..21ff0f86841 100644 --- a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/OptionsParser.java +++ b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/OptionsParser.java @@ -32,7 +32,6 @@ import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.fs.Path; -import org.apache.hadoop.tools.DistCpOptions.FileAttribute; import com.google.common.base.Preconditions; @@ -95,253 +94,126 @@ public class OptionsParser { Arrays.toString(args), e); } - DistCpOptions option = parseSourceAndTargetPaths(command); - - option.setIgnoreFailures( - command.hasOption(DistCpOptionSwitch.IGNORE_FAILURES.getSwitch())); - - option.setAtomicCommit( - command.hasOption(DistCpOptionSwitch.ATOMIC_COMMIT.getSwitch())); - - option.setSyncFolder( - command.hasOption(DistCpOptionSwitch.SYNC_FOLDERS.getSwitch())); - - option.setOverwrite( - command.hasOption(DistCpOptionSwitch.OVERWRITE.getSwitch())); - - option.setAppend( - command.hasOption(DistCpOptionSwitch.APPEND.getSwitch())); - - option.setDeleteMissing( - command.hasOption(DistCpOptionSwitch.DELETE_MISSING.getSwitch())); - - option.setSkipCRC( - command.hasOption(DistCpOptionSwitch.SKIP_CRC.getSwitch())); - - if (command.hasOption(DistCpOptionSwitch.WORK_PATH.getSwitch()) && - option.shouldAtomicCommit()) { - String workPath = getVal(command, DistCpOptionSwitch.WORK_PATH.getSwitch()); - if (workPath != null && !workPath.isEmpty()) { - option.setAtomicWorkPath(new Path(workPath)); - } - } else if (command.hasOption(DistCpOptionSwitch.WORK_PATH.getSwitch())) { - throw new IllegalArgumentException("-tmp work-path can only be specified along with -atomic"); - } - - if (command.hasOption(DistCpOptionSwitch.LOG_PATH.getSwitch())) { - option.setLogPath(new Path(getVal(command, DistCpOptionSwitch.LOG_PATH.getSwitch()))); - } - - - if (command.hasOption(DistCpOptionSwitch.BLOCKING.getSwitch())) { - option.setBlocking(false); - } - - parseBandwidth(command, option); - - parseNumListStatusThreads(command, option); - - parseMaxMaps(command, option); - - if (command.hasOption(DistCpOptionSwitch.COPY_STRATEGY.getSwitch())) { - option.setCopyStrategy( - getVal(command, DistCpOptionSwitch.COPY_STRATEGY.getSwitch())); - } - - parsePreserveStatus(command, option); + DistCpOptions.Builder builder = parseSourceAndTargetPaths(command); + builder + .withAtomicCommit( + command.hasOption(DistCpOptionSwitch.ATOMIC_COMMIT.getSwitch())) + .withSyncFolder( + command.hasOption(DistCpOptionSwitch.SYNC_FOLDERS.getSwitch())) + .withDeleteMissing( + command.hasOption(DistCpOptionSwitch.DELETE_MISSING.getSwitch())) + .withIgnoreFailures( + command.hasOption(DistCpOptionSwitch.IGNORE_FAILURES.getSwitch())) + .withOverwrite( + command.hasOption(DistCpOptionSwitch.OVERWRITE.getSwitch())) + .withAppend( + command.hasOption(DistCpOptionSwitch.APPEND.getSwitch())) + .withCRC( + command.hasOption(DistCpOptionSwitch.SKIP_CRC.getSwitch())) + .withBlocking( + !command.hasOption(DistCpOptionSwitch.BLOCKING.getSwitch())); if (command.hasOption(DistCpOptionSwitch.DIFF.getSwitch())) { String[] snapshots = getVals(command, DistCpOptionSwitch.DIFF.getSwitch()); checkSnapshotsArgs(snapshots); - option.setUseDiff(snapshots[0], snapshots[1]); + builder.withUseDiff(snapshots[0], snapshots[1]); } if (command.hasOption(DistCpOptionSwitch.RDIFF.getSwitch())) { String[] snapshots = getVals(command, DistCpOptionSwitch.RDIFF.getSwitch()); checkSnapshotsArgs(snapshots); - option.setUseRdiff(snapshots[0], snapshots[1]); + builder.withUseRdiff(snapshots[0], snapshots[1]); } - parseFileLimit(command); - - parseSizeLimit(command); - if (command.hasOption(DistCpOptionSwitch.FILTERS.getSwitch())) { - option.setFiltersFile(getVal(command, - DistCpOptionSwitch.FILTERS.getSwitch())); + builder.withFiltersFile( + getVal(command, DistCpOptionSwitch.FILTERS.getSwitch())); } - parseBlocksPerChunk(command, option); + if (command.hasOption(DistCpOptionSwitch.LOG_PATH.getSwitch())) { + builder.withLogPath( + new Path(getVal(command, DistCpOptionSwitch.LOG_PATH.getSwitch()))); + } - option.validate(); - - return option; - } - - - /** - * A helper method to parse chunk size in number of blocks. - * Used when breaking large file into chunks to copy in parallel. - * - * @param command command line arguments - */ - private static void parseBlocksPerChunk(CommandLine command, - DistCpOptions option) { - boolean hasOption = - command.hasOption(DistCpOptionSwitch.BLOCKS_PER_CHUNK.getSwitch()); - LOG.info("parseChunkSize: " + - DistCpOptionSwitch.BLOCKS_PER_CHUNK.getSwitch() + " " + hasOption); - if (hasOption) { - String chunkSizeString = getVal(command, - DistCpOptionSwitch.BLOCKS_PER_CHUNK.getSwitch().trim()); - try { - int csize = Integer.parseInt(chunkSizeString); - if (csize < 0) { - csize = 0; - } - LOG.info("Set distcp blocksPerChunk to " + csize); - option.setBlocksPerChunk(csize); - } - catch (NumberFormatException e) { - throw new IllegalArgumentException("blocksPerChunk is invalid: " - + chunkSizeString, e); + if (command.hasOption(DistCpOptionSwitch.WORK_PATH.getSwitch())) { + final String workPath = getVal(command, + DistCpOptionSwitch.WORK_PATH.getSwitch()); + if (workPath != null && !workPath.isEmpty()) { + builder.withAtomicWorkPath(new Path(workPath)); } } - } - /** - * parseSizeLimit is a helper method for parsing the deprecated - * argument SIZE_LIMIT. - * - * @param command command line arguments - */ - private static void parseSizeLimit(CommandLine command) { - if (command.hasOption(DistCpOptionSwitch.SIZE_LIMIT.getSwitch())) { - String sizeLimitString = getVal(command, - DistCpOptionSwitch.SIZE_LIMIT.getSwitch().trim()); + if (command.hasOption(DistCpOptionSwitch.BANDWIDTH.getSwitch())) { try { - Long.parseLong(sizeLimitString); - } - catch (NumberFormatException e) { - throw new IllegalArgumentException("Size-limit is invalid: " - + sizeLimitString, e); - } - LOG.warn(DistCpOptionSwitch.SIZE_LIMIT.getSwitch() + " is a deprecated" + - " option. Ignoring."); - } - } - - /** - * parseFileLimit is a helper method for parsing the deprecated - * argument FILE_LIMIT. - * - * @param command command line arguments - */ - private static void parseFileLimit(CommandLine command) { - if (command.hasOption(DistCpOptionSwitch.FILE_LIMIT.getSwitch())) { - String fileLimitString = getVal(command, - DistCpOptionSwitch.FILE_LIMIT.getSwitch().trim()); - try { - Integer.parseInt(fileLimitString); + final Float mapBandwidth = Float.parseFloat( + getVal(command, DistCpOptionSwitch.BANDWIDTH.getSwitch())); + builder.withMapBandwidth(mapBandwidth); } catch (NumberFormatException e) { - throw new IllegalArgumentException("File-limit is invalid: " - + fileLimitString, e); - } - LOG.warn(DistCpOptionSwitch.FILE_LIMIT.getSwitch() + " is a deprecated" + - " option. Ignoring."); - } - } - - /** - * parsePreserveStatus is a helper method for parsing PRESERVE_STATUS. - * - * @param command command line arguments - * @param option parsed distcp options - */ - private static void parsePreserveStatus(CommandLine command, - DistCpOptions option) { - if (command.hasOption(DistCpOptionSwitch.PRESERVE_STATUS.getSwitch())) { - String attributes = - getVal(command, DistCpOptionSwitch.PRESERVE_STATUS.getSwitch()); - if (attributes == null || attributes.isEmpty()) { - for (FileAttribute attribute : FileAttribute.values()) { - option.preserve(attribute); - } - } else { - for (int index = 0; index < attributes.length(); index++) { - option.preserve(FileAttribute. - getAttribute(attributes.charAt(index))); - } + throw new IllegalArgumentException("Bandwidth specified is invalid: " + + getVal(command, DistCpOptionSwitch.BANDWIDTH.getSwitch()), e); } } - } - /** - * parseMaxMaps is a helper method for parsing MAX_MAPS. - * - * @param command command line arguments - * @param option parsed distcp options - */ - private static void parseMaxMaps(CommandLine command, - DistCpOptions option) { - if (command.hasOption(DistCpOptionSwitch.MAX_MAPS.getSwitch())) { - try { - Integer maps = Integer.parseInt( - getVal(command, DistCpOptionSwitch.MAX_MAPS.getSwitch()).trim()); - option.setMaxMaps(maps); - } catch (NumberFormatException e) { - throw new IllegalArgumentException("Number of maps is invalid: " + - getVal(command, DistCpOptionSwitch.MAX_MAPS.getSwitch()), e); - } - } - } - - /** - * parseNumListStatusThreads is a helper method for parsing - * NUM_LISTSTATUS_THREADS. - * - * @param command command line arguments - * @param option parsed distcp options - */ - private static void parseNumListStatusThreads(CommandLine command, - DistCpOptions option) { if (command.hasOption( DistCpOptionSwitch.NUM_LISTSTATUS_THREADS.getSwitch())) { try { - Integer numThreads = Integer.parseInt(getVal(command, - DistCpOptionSwitch.NUM_LISTSTATUS_THREADS.getSwitch()).trim()); - option.setNumListstatusThreads(numThreads); + final Integer numThreads = Integer.parseInt(getVal(command, + DistCpOptionSwitch.NUM_LISTSTATUS_THREADS.getSwitch())); + builder.withNumListstatusThreads(numThreads); } catch (NumberFormatException e) { throw new IllegalArgumentException( "Number of liststatus threads is invalid: " + getVal(command, DistCpOptionSwitch.NUM_LISTSTATUS_THREADS.getSwitch()), e); } } - } - /** - * parseBandwidth is a helper method for parsing BANDWIDTH. - * - * @param command command line arguments - * @param option parsed distcp options - */ - private static void parseBandwidth(CommandLine command, - DistCpOptions option) { - if (command.hasOption(DistCpOptionSwitch.BANDWIDTH.getSwitch())) { + if (command.hasOption(DistCpOptionSwitch.MAX_MAPS.getSwitch())) { try { - Float mapBandwidth = Float.parseFloat( - getVal(command, DistCpOptionSwitch.BANDWIDTH.getSwitch()).trim()); - if (mapBandwidth <= 0) { - throw new IllegalArgumentException("Bandwidth specified is not " + - "positive: " + mapBandwidth); - } - option.setMapBandwidth(mapBandwidth); + final Integer maps = Integer.parseInt( + getVal(command, DistCpOptionSwitch.MAX_MAPS.getSwitch())); + builder.maxMaps(maps); } catch (NumberFormatException e) { - throw new IllegalArgumentException("Bandwidth specified is invalid: " + - getVal(command, DistCpOptionSwitch.BANDWIDTH.getSwitch()), e); + throw new IllegalArgumentException("Number of maps is invalid: " + + getVal(command, DistCpOptionSwitch.MAX_MAPS.getSwitch()), e); } } + + if (command.hasOption(DistCpOptionSwitch.COPY_STRATEGY.getSwitch())) { + builder.withCopyStrategy( + getVal(command, DistCpOptionSwitch.COPY_STRATEGY.getSwitch())); + } + + if (command.hasOption(DistCpOptionSwitch.PRESERVE_STATUS.getSwitch())) { + builder.preserve( + getVal(command, DistCpOptionSwitch.PRESERVE_STATUS.getSwitch())); + } + + if (command.hasOption(DistCpOptionSwitch.FILE_LIMIT.getSwitch())) { + LOG.warn(DistCpOptionSwitch.FILE_LIMIT.getSwitch() + " is a deprecated" + + " option. Ignoring."); + } + + if (command.hasOption(DistCpOptionSwitch.SIZE_LIMIT.getSwitch())) { + LOG.warn(DistCpOptionSwitch.SIZE_LIMIT.getSwitch() + " is a deprecated" + + " option. Ignoring."); + } + + if (command.hasOption(DistCpOptionSwitch.BLOCKS_PER_CHUNK.getSwitch())) { + final String chunkSizeStr = getVal(command, + DistCpOptionSwitch.BLOCKS_PER_CHUNK.getSwitch().trim()); + try { + int csize = Integer.parseInt(chunkSizeStr); + csize = csize > 0 ? csize : 0; + LOG.info("Set distcp blocksPerChunk to " + csize); + builder.withBlocksPerChunk(csize); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("blocksPerChunk is invalid: " + + chunkSizeStr, e); + } + } + + return builder.build(); } /** @@ -351,9 +223,8 @@ public class OptionsParser { * @param command command line arguments * @return DistCpOptions */ - private static DistCpOptions parseSourceAndTargetPaths( + private static DistCpOptions.Builder parseSourceAndTargetPaths( CommandLine command) { - DistCpOptions option; Path targetPath; List sourcePaths = new ArrayList(); @@ -378,20 +249,22 @@ public class OptionsParser { throw new IllegalArgumentException("Both source file listing and " + "source paths present"); } - option = new DistCpOptions(new Path(getVal(command, DistCpOptionSwitch. - SOURCE_FILE_LISTING.getSwitch())), targetPath); + return new DistCpOptions.Builder(new Path(getVal(command, + DistCpOptionSwitch.SOURCE_FILE_LISTING.getSwitch())), targetPath); } else { if (sourcePaths.isEmpty()) { throw new IllegalArgumentException("Neither source file listing nor " + "source paths present"); } - option = new DistCpOptions(sourcePaths, targetPath); + return new DistCpOptions.Builder(sourcePaths, targetPath); } - return option; } private static String getVal(CommandLine command, String swtch) { - String optionValue = command.getOptionValue(swtch); + if (swtch == null) { + return null; + } + String optionValue = command.getOptionValue(swtch.trim()); if (optionValue == null) { return null; } else { diff --git a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/SimpleCopyListing.java b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/SimpleCopyListing.java index af913474550..8111b047571 100644 --- a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/SimpleCopyListing.java +++ b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/SimpleCopyListing.java @@ -123,10 +123,10 @@ public class SimpleCopyListing extends CopyListing { } @Override - protected void validatePaths(DistCpOptions options) + protected void validatePaths(DistCpContext context) throws IOException, InvalidInputException { - Path targetPath = options.getTargetPath(); + Path targetPath = context.getTargetPath(); FileSystem targetFS = targetPath.getFileSystem(getConf()); boolean targetExists = false; boolean targetIsFile = false; @@ -142,12 +142,12 @@ public class SimpleCopyListing extends CopyListing { //If target is a file, then source has to be single file if (targetIsFile) { - if (options.getSourcePaths().size() > 1) { + if (context.getSourcePaths().size() > 1) { throw new InvalidInputException("Multiple source being copied to a file: " + targetPath); } - Path srcPath = options.getSourcePaths().get(0); + Path srcPath = context.getSourcePaths().get(0); FileSystem sourceFS = srcPath.getFileSystem(getConf()); if (!sourceFS.isFile(srcPath)) { throw new InvalidInputException("Cannot copy " + srcPath + @@ -155,12 +155,12 @@ public class SimpleCopyListing extends CopyListing { } } - if (options.shouldAtomicCommit() && targetExists) { + if (context.shouldAtomicCommit() && targetExists) { throw new InvalidInputException("Target path for atomic-commit already exists: " + targetPath + ". Cannot atomic-commit to pre-existing target-path."); } - for (Path path: options.getSourcePaths()) { + for (Path path: context.getSourcePaths()) { FileSystem fs = path.getFileSystem(getConf()); if (!fs.exists(path)) { throw new InvalidInputException(path + " doesn't exist"); @@ -184,7 +184,7 @@ public class SimpleCopyListing extends CopyListing { } if (targetIsReservedRaw) { - options.preserveRawXattrs(); + context.setPreserveRawXattrs(true); getConf().setBoolean(DistCpConstants.CONF_LABEL_PRESERVE_RAWXATTRS, true); } @@ -194,18 +194,19 @@ public class SimpleCopyListing extends CopyListing { */ Credentials credentials = getCredentials(); if (credentials != null) { - Path[] inputPaths = options.getSourcePaths().toArray(new Path[1]); + Path[] inputPaths = context.getSourcePaths() + .toArray(new Path[1]); TokenCache.obtainTokensForNamenodes(credentials, inputPaths, getConf()); } } @Override protected void doBuildListing(Path pathToListingFile, - DistCpOptions options) throws IOException { - if(options.shouldUseSnapshotDiff()) { - doBuildListingWithSnapshotDiff(getWriter(pathToListingFile), options); - }else { - doBuildListing(getWriter(pathToListingFile), options); + DistCpContext context) throws IOException { + if (context.shouldUseSnapshotDiff()) { + doBuildListingWithSnapshotDiff(getWriter(pathToListingFile), context); + } else { + doBuildListing(getWriter(pathToListingFile), context); } } @@ -232,22 +233,22 @@ public class SimpleCopyListing extends CopyListing { * @throws IOException */ private void addToFileListing(SequenceFile.Writer fileListWriter, - Path sourceRoot, Path path, DistCpOptions options) throws IOException { + Path sourceRoot, Path path, DistCpContext context) throws IOException { sourceRoot = getPathWithSchemeAndAuthority(sourceRoot); path = getPathWithSchemeAndAuthority(path); path = makeQualified(path); FileSystem sourceFS = sourceRoot.getFileSystem(getConf()); FileStatus fileStatus = sourceFS.getFileStatus(path); - final boolean preserveAcls = options.shouldPreserve(FileAttribute.ACL); - final boolean preserveXAttrs = options.shouldPreserve(FileAttribute.XATTR); - final boolean preserveRawXAttrs = options.shouldPreserveRawXattrs(); + final boolean preserveAcls = context.shouldPreserve(FileAttribute.ACL); + final boolean preserveXAttrs = context.shouldPreserve(FileAttribute.XATTR); + final boolean preserveRawXAttrs = context.shouldPreserveRawXattrs(); LinkedList fileCopyListingStatus = DistCpUtils.toCopyListingFileStatus(sourceFS, fileStatus, preserveAcls, preserveXAttrs, preserveRawXAttrs, - options.getBlocksPerChunk()); + context.getBlocksPerChunk()); writeToFileListingRoot(fileListWriter, fileCopyListingStatus, - sourceRoot, options); + sourceRoot, context); } /** @@ -258,14 +259,16 @@ public class SimpleCopyListing extends CopyListing { * {@link org.apache.hadoop.tools.DistCpSync#sync}. An item can be * created/modified and renamed, in which case, the target path is put * into the list. + * @param fileListWriter the list for holding processed results + * @param context The DistCp context with associated input options * @throws IOException */ @VisibleForTesting protected void doBuildListingWithSnapshotDiff( - SequenceFile.Writer fileListWriter, DistCpOptions options) + SequenceFile.Writer fileListWriter, DistCpContext context) throws IOException { ArrayList diffList = distCpSync.prepareDiffListForCopyListing(); - Path sourceRoot = options.getSourcePaths().get(0); + Path sourceRoot = context.getSourcePaths().get(0); FileSystem sourceFS = sourceRoot.getFileSystem(getConf()); try { @@ -273,13 +276,13 @@ public class SimpleCopyListing extends CopyListing { for (DiffInfo diff : diffList) { // add snapshot paths prefix diff.setTarget( - new Path(options.getSourcePaths().get(0), diff.getTarget())); + new Path(context.getSourcePaths().get(0), diff.getTarget())); if (diff.getType() == SnapshotDiffReport.DiffType.MODIFY) { addToFileListing(fileListWriter, - sourceRoot, diff.getTarget(), options); + sourceRoot, diff.getTarget(), context); } else if (diff.getType() == SnapshotDiffReport.DiffType.CREATE) { addToFileListing(fileListWriter, - sourceRoot, diff.getTarget(), options); + sourceRoot, diff.getTarget(), context); FileStatus sourceStatus = sourceFS.getFileStatus(diff.getTarget()); if (sourceStatus.isDirectory()) { @@ -290,13 +293,13 @@ public class SimpleCopyListing extends CopyListing { HashSet excludeList = distCpSync.getTraverseExcludeList(diff.getSource(), - options.getSourcePaths().get(0)); + context.getSourcePaths().get(0)); ArrayList sourceDirs = new ArrayList<>(); sourceDirs.add(sourceStatus); traverseDirectory(fileListWriter, sourceFS, sourceDirs, - sourceRoot, options, excludeList, fileStatuses); + sourceRoot, context, excludeList, fileStatuses); } } } @@ -325,27 +328,30 @@ public class SimpleCopyListing extends CopyListing { * See computeSourceRootPath method for how the root path of the source is * computed. * @param fileListWriter - * @param options + * @param context The distcp context with associated input options * @throws IOException */ @VisibleForTesting protected void doBuildListing(SequenceFile.Writer fileListWriter, - DistCpOptions options) throws IOException { - if (options.getNumListstatusThreads() > 0) { - numListstatusThreads = options.getNumListstatusThreads(); + DistCpContext context) throws IOException { + if (context.getNumListstatusThreads() > 0) { + numListstatusThreads = context.getNumListstatusThreads(); } try { List statusList = Lists.newArrayList(); - for (Path path: options.getSourcePaths()) { + for (Path path: context.getSourcePaths()) { FileSystem sourceFS = path.getFileSystem(getConf()); - final boolean preserveAcls = options.shouldPreserve(FileAttribute.ACL); - final boolean preserveXAttrs = options.shouldPreserve(FileAttribute.XATTR); - final boolean preserveRawXAttrs = options.shouldPreserveRawXattrs(); + final boolean preserveAcls = + context.shouldPreserve(FileAttribute.ACL); + final boolean preserveXAttrs = + context.shouldPreserve(FileAttribute.XATTR); + final boolean preserveRawXAttrs = + context.shouldPreserveRawXattrs(); path = makeQualified(path); FileStatus rootStatus = sourceFS.getFileStatus(path); - Path sourcePathRoot = computeSourceRootPath(rootStatus, options); + Path sourcePathRoot = computeSourceRootPath(rootStatus, context); FileStatus[] sourceFiles = sourceFS.listStatus(path); boolean explore = (sourceFiles != null && sourceFiles.length > 0); @@ -353,9 +359,9 @@ public class SimpleCopyListing extends CopyListing { LinkedList rootCopyListingStatus = DistCpUtils.toCopyListingFileStatus(sourceFS, rootStatus, preserveAcls, preserveXAttrs, preserveRawXAttrs, - options.getBlocksPerChunk()); + context.getBlocksPerChunk()); writeToFileListingRoot(fileListWriter, rootCopyListingStatus, - sourcePathRoot, options); + sourcePathRoot, context); } if (explore) { ArrayList sourceDirs = new ArrayList(); @@ -368,7 +374,7 @@ public class SimpleCopyListing extends CopyListing { preserveAcls && sourceStatus.isDirectory(), preserveXAttrs && sourceStatus.isDirectory(), preserveRawXAttrs && sourceStatus.isDirectory(), - options.getBlocksPerChunk()); + context.getBlocksPerChunk()); for (CopyListingFileStatus fs : sourceCopyListingStatus) { if (randomizeFileListing) { addToFileListing(statusList, @@ -385,7 +391,7 @@ public class SimpleCopyListing extends CopyListing { } } traverseDirectory(fileListWriter, sourceFS, sourceDirs, - sourcePathRoot, options, null, statusList); + sourcePathRoot, context, null, statusList); } } if (randomizeFileListing) { @@ -447,13 +453,13 @@ public class SimpleCopyListing extends CopyListing { } private Path computeSourceRootPath(FileStatus sourceStatus, - DistCpOptions options) throws IOException { + DistCpContext context) throws IOException { - Path target = options.getTargetPath(); + Path target = context.getTargetPath(); FileSystem targetFS = target.getFileSystem(getConf()); - final boolean targetPathExists = options.getTargetPathExists(); + final boolean targetPathExists = context.isTargetPathExists(); - boolean solitaryFile = options.getSourcePaths().size() == 1 + boolean solitaryFile = context.getSourcePaths().size() == 1 && !sourceStatus.isDirectory(); if (solitaryFile) { @@ -463,8 +469,11 @@ public class SimpleCopyListing extends CopyListing { return sourceStatus.getPath().getParent(); } } else { - boolean specialHandling = (options.getSourcePaths().size() == 1 && !targetPathExists) || - options.shouldSyncFolder() || options.shouldOverwrite(); + boolean specialHandling = + (context.getSourcePaths().size() == 1 && + !targetPathExists) || + context.shouldSyncFolder() || + context.shouldOverwrite(); if ((specialHandling && sourceStatus.isDirectory()) || sourceStatus.getPath().isRoot()) { @@ -610,13 +619,13 @@ public class SimpleCopyListing extends CopyListing { FileSystem sourceFS, ArrayList sourceDirs, Path sourcePathRoot, - DistCpOptions options, + DistCpContext context, HashSet excludeList, List fileStatuses) throws IOException { - final boolean preserveAcls = options.shouldPreserve(FileAttribute.ACL); - final boolean preserveXAttrs = options.shouldPreserve(FileAttribute.XATTR); - final boolean preserveRawXattrs = options.shouldPreserveRawXattrs(); + final boolean preserveAcls = context.shouldPreserve(FileAttribute.ACL); + final boolean preserveXAttrs = context.shouldPreserve(FileAttribute.XATTR); + final boolean preserveRawXattrs = context.shouldPreserveRawXattrs(); assert numListstatusThreads > 0; if (LOG.isDebugEnabled()) { @@ -649,7 +658,7 @@ public class SimpleCopyListing extends CopyListing { preserveAcls && child.isDirectory(), preserveXAttrs && child.isDirectory(), preserveRawXattrs && child.isDirectory(), - options.getBlocksPerChunk()); + context.getBlocksPerChunk()); for (CopyListingFileStatus fs : childCopyListingStatus) { if (randomizeFileListing) { @@ -681,9 +690,9 @@ public class SimpleCopyListing extends CopyListing { private void writeToFileListingRoot(SequenceFile.Writer fileListWriter, LinkedList fileStatus, Path sourcePathRoot, - DistCpOptions options) throws IOException { - boolean syncOrOverwrite = options.shouldSyncFolder() || - options.shouldOverwrite(); + DistCpContext context) throws IOException { + boolean syncOrOverwrite = context.shouldSyncFolder() || + context.shouldOverwrite(); for (CopyListingFileStatus fs : fileStatus) { if (fs.getPath().equals(sourcePathRoot) && fs.isDirectory() && syncOrOverwrite) { diff --git a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/CopyCommitter.java b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/CopyCommitter.java index 6ddaab99c3d..81c2be7e05d 100644 --- a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/CopyCommitter.java +++ b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/mapred/CopyCommitter.java @@ -36,6 +36,7 @@ import org.apache.hadoop.tools.CopyListing; import org.apache.hadoop.tools.CopyListingFileStatus; import org.apache.hadoop.tools.DistCpConstants; import org.apache.hadoop.tools.DistCpOptionSwitch; +import org.apache.hadoop.tools.DistCpContext; import org.apache.hadoop.tools.DistCpOptions; import org.apache.hadoop.tools.DistCpOptions.FileAttribute; import org.apache.hadoop.tools.GlobbedCopyListing; @@ -354,16 +355,18 @@ public class CopyCommitter extends FileOutputCommitter { Path resultNonePath = Path.getPathWithoutSchemeAndAuthority(targetFinalPath) .toString().startsWith(DistCpConstants.HDFS_RESERVED_RAW_DIRECTORY_NAME) ? DistCpConstants.RAW_NONE_PATH : DistCpConstants.NONE_PATH; - DistCpOptions options = new DistCpOptions(targets, resultNonePath); // // Set up options to be the same from the CopyListing.buildListing's perspective, // so to collect similar listings as when doing the copy // - options.setOverwrite(overwrite); - options.setSyncFolder(syncFolder); - options.setTargetPathExists(targetPathExists); - - target.buildListing(targetListing, options); + DistCpOptions options = new DistCpOptions.Builder(targets, resultNonePath) + .withOverwrite(overwrite) + .withSyncFolder(syncFolder) + .build(); + DistCpContext distCpContext = new DistCpContext(options); + distCpContext.setTargetPathExists(targetPathExists); + + target.buildListing(targetListing, distCpContext); Path sortedTargetListing = DistCpUtils.sortListing(clusterFS, conf, targetListing); long totalLen = clusterFS.getFileStatus(sortedTargetListing).getLen(); diff --git a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/util/DistCpUtils.java b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/util/DistCpUtils.java index e315b848de4..dbe750a65c4 100644 --- a/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/util/DistCpUtils.java +++ b/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/util/DistCpUtils.java @@ -39,7 +39,7 @@ import org.apache.hadoop.mapreduce.InputFormat; import org.apache.hadoop.tools.CopyListing.AclsNotSupportedException; import org.apache.hadoop.tools.CopyListing.XAttrsNotSupportedException; import org.apache.hadoop.tools.CopyListingFileStatus; -import org.apache.hadoop.tools.DistCpOptions; +import org.apache.hadoop.tools.DistCpContext; import org.apache.hadoop.tools.DistCpOptions.FileAttribute; import org.apache.hadoop.tools.mapred.UniformSizeInputFormat; import org.apache.hadoop.util.StringUtils; @@ -116,13 +116,13 @@ public class DistCpUtils { * a particular strategy from distcp-default.xml * * @param conf - Configuration object - * @param options - Handle to input options + * @param context - Distcp context with associated input options * @return Class implementing the strategy specified in options. */ public static Class getStrategy(Configuration conf, - DistCpOptions options) { + DistCpContext context) { String confLabel = "distcp." - + StringUtils.toLowerCase(options.getCopyStrategy()) + + StringUtils.toLowerCase(context.getCopyStrategy()) + ".strategy" + ".impl"; return conf.getClass(confLabel, UniformSizeInputFormat.class, InputFormat.class); } diff --git a/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestCopyListing.java b/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestCopyListing.java index ea63e235138..97a6f62444b 100644 --- a/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestCopyListing.java +++ b/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestCopyListing.java @@ -103,20 +103,19 @@ public class TestCopyListing extends SimpleCopyListing { List srcPaths = new ArrayList(); srcPaths.add(new Path("/tmp/in/1")); srcPaths.add(new Path("/tmp/in/2")); - Path target = new Path("/tmp/out/1"); + final Path target = new Path("/tmp/out/1"); TestDistCpUtils.createFile(fs, "/tmp/in/1"); TestDistCpUtils.createFile(fs, "/tmp/in/2"); fs.mkdirs(target); - DistCpOptions options = new DistCpOptions(srcPaths, target); - validatePaths(options); + final DistCpOptions options = new DistCpOptions.Builder(srcPaths, target) + .build(); + validatePaths(new DistCpContext(options)); TestDistCpUtils.delete(fs, "/tmp"); //No errors - target = new Path("/tmp/out/1"); fs.create(target).close(); - options = new DistCpOptions(srcPaths, target); try { - validatePaths(options); + validatePaths(new DistCpContext(options)); Assert.fail("Invalid inputs accepted"); } catch (InvalidInputException ignore) { } TestDistCpUtils.delete(fs, "/tmp"); @@ -124,11 +123,9 @@ public class TestCopyListing extends SimpleCopyListing { srcPaths.clear(); srcPaths.add(new Path("/tmp/in/1")); fs.mkdirs(new Path("/tmp/in/1")); - target = new Path("/tmp/out/1"); fs.create(target).close(); - options = new DistCpOptions(srcPaths, target); try { - validatePaths(options); + validatePaths(new DistCpContext(options)); Assert.fail("Invalid inputs accepted"); } catch (InvalidInputException ignore) { } TestDistCpUtils.delete(fs, "/tmp"); @@ -151,10 +148,13 @@ public class TestCopyListing extends SimpleCopyListing { TestDistCpUtils.createFile(fs, "/tmp/in/src2/1.txt"); Path target = new Path("/tmp/out"); Path listingFile = new Path("/tmp/list"); - DistCpOptions options = new DistCpOptions(srcPaths, target); - CopyListing listing = CopyListing.getCopyListing(getConf(), CREDENTIALS, options); + final DistCpOptions options = new DistCpOptions.Builder(srcPaths, target) + .build(); + final DistCpContext context = new DistCpContext(options); + CopyListing listing = CopyListing.getCopyListing(getConf(), CREDENTIALS, + context); try { - listing.buildListing(listingFile, options); + listing.buildListing(listingFile, context); Assert.fail("Duplicates not detected"); } catch (DuplicateFileException ignore) { } @@ -196,11 +196,12 @@ public class TestCopyListing extends SimpleCopyListing { Path listingFile = new Path("/tmp/file"); - DistCpOptions options = new DistCpOptions(srcPaths, target); - options.setSyncFolder(true); + final DistCpOptions options = new DistCpOptions.Builder(srcPaths, target) + .withSyncFolder(true) + .build(); CopyListing listing = new SimpleCopyListing(getConf(), CREDENTIALS); try { - listing.buildListing(listingFile, options); + listing.buildListing(listingFile, new DistCpContext(options)); Assert.fail("Duplicates not detected"); } catch (DuplicateFileException ignore) { } @@ -209,7 +210,7 @@ public class TestCopyListing extends SimpleCopyListing { TestDistCpUtils.delete(fs, "/tmp"); try { - listing.buildListing(listingFile, options); + listing.buildListing(listingFile, new DistCpContext(options)); Assert.fail("Invalid input not detected"); } catch (InvalidInputException ignore) { } @@ -244,14 +245,14 @@ public class TestCopyListing extends SimpleCopyListing { } Path listingFile = new Path("/tmp/file"); - DistCpOptions options = new DistCpOptions(srcPaths, target); - options.setSyncFolder(true); + final DistCpOptions options = new DistCpOptions.Builder(srcPaths, target) + .withSyncFolder(true).build(); // Check without randomizing files getConf().setBoolean( DistCpConstants.CONF_LABEL_SIMPLE_LISTING_RANDOMIZE_FILES, false); SimpleCopyListing listing = new SimpleCopyListing(getConf(), CREDENTIALS); - listing.buildListing(listingFile, options); + listing.buildListing(listingFile, new DistCpContext(options)); Assert.assertEquals(listing.getNumberOfPaths(), pathCount); validateFinalListing(listingFile, srcFiles); @@ -265,7 +266,7 @@ public class TestCopyListing extends SimpleCopyListing { // Set the seed for randomness, so that it can be verified later long seed = System.nanoTime(); listing.setSeedForRandomListing(seed); - listing.buildListing(listingFile, options); + listing.buildListing(listingFile, new DistCpContext(options)); Assert.assertEquals(listing.getNumberOfPaths(), pathCount); // validate randomness @@ -322,11 +323,12 @@ public class TestCopyListing extends SimpleCopyListing { List srcPaths = new ArrayList(); srcPaths.add(sourceFile); - DistCpOptions options = new DistCpOptions(srcPaths, targetFile); + DistCpOptions options = new DistCpOptions.Builder(srcPaths, targetFile) + .build(); CopyListing listing = new SimpleCopyListing(getConf(), CREDENTIALS); final Path listFile = new Path(testRoot, "/tmp/fileList.seq"); - listing.buildListing(listFile, options); + listing.buildListing(listFile, new DistCpContext(options)); reader = new SequenceFile.Reader(getConf(), SequenceFile.Reader.file(listFile)); @@ -359,10 +361,11 @@ public class TestCopyListing extends SimpleCopyListing { doThrow(expectedEx).when(writer).close(); SimpleCopyListing listing = new SimpleCopyListing(getConf(), CREDENTIALS); - DistCpOptions options = new DistCpOptions(srcs, new Path(outFile.toURI())); + final DistCpOptions options = new DistCpOptions.Builder(srcs, + new Path(outFile.toURI())).build(); Exception actualEx = null; try { - listing.doBuildListing(writer, options); + listing.doBuildListing(writer, new DistCpContext(options)); } catch (Exception e) { actualEx = e; } diff --git a/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestDistCpOptions.java b/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestDistCpOptions.java new file mode 100644 index 00000000000..35251943c02 --- /dev/null +++ b/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestDistCpOptions.java @@ -0,0 +1,500 @@ +/** + * 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.tools; + +import java.util.Collections; + +import org.junit.Assert; +import org.junit.Test; + +import org.apache.hadoop.fs.Path; +import org.apache.hadoop.tools.DistCpOptions.FileAttribute; + +import static org.apache.hadoop.test.GenericTestUtils.assertExceptionContains; +import static org.apache.hadoop.tools.DistCpOptions.MAX_NUM_LISTSTATUS_THREADS; +import static org.junit.Assert.fail; + +/** + * This is to test constructing {@link DistCpOptions} manually with setters. + * + * The test cases in this class is very similar to the parser test, see + * {@link TestOptionsParser}. + */ +public class TestDistCpOptions { + + private static final float DELTA = 0.001f; + + @Test + public void testSetIgnoreFailure() { + final DistCpOptions.Builder builder = new DistCpOptions.Builder( + Collections.singletonList(new Path("hdfs://localhost:8020/source")), + new Path("hdfs://localhost:8020/target/")); + Assert.assertFalse(builder.build().shouldIgnoreFailures()); + + builder.withIgnoreFailures(true); + Assert.assertTrue(builder.build().shouldIgnoreFailures()); + } + + @Test + public void testSetOverwrite() { + final DistCpOptions.Builder builder = new DistCpOptions.Builder( + Collections.singletonList(new Path("hdfs://localhost:8020/source")), + new Path("hdfs://localhost:8020/target/")); + Assert.assertFalse(builder.build().shouldOverwrite()); + + builder.withOverwrite(true); + Assert.assertTrue(builder.build().shouldOverwrite()); + + try { + builder.withSyncFolder(true).build(); + Assert.fail("Update and overwrite aren't allowed together"); + } catch (IllegalArgumentException ignore) { + } + } + + @Test + public void testLogPath() { + final DistCpOptions.Builder builder = new DistCpOptions.Builder( + Collections.singletonList(new Path("hdfs://localhost:8020/source")), + new Path("hdfs://localhost:8020/target/")); + Assert.assertNull(builder.build().getLogPath()); + + final Path logPath = new Path("hdfs://localhost:8020/logs"); + builder.withLogPath(logPath); + Assert.assertEquals(logPath, builder.build().getLogPath()); + } + + @Test + public void testSetBlokcing() { + final DistCpOptions.Builder builder = new DistCpOptions.Builder( + Collections.singletonList(new Path("hdfs://localhost:8020/source")), + new Path("hdfs://localhost:8020/target/")); + Assert.assertTrue(builder.build().shouldBlock()); + + builder.withBlocking(false); + Assert.assertFalse(builder.build().shouldBlock()); + } + + @Test + public void testSetBandwidth() { + final DistCpOptions.Builder builder = new DistCpOptions.Builder( + Collections.singletonList(new Path("hdfs://localhost:8020/source")), + new Path("hdfs://localhost:8020/target/")); + Assert.assertEquals(0, builder.build().getMapBandwidth(), DELTA); + + builder.withMapBandwidth(11); + Assert.assertEquals(11, builder.build().getMapBandwidth(), DELTA); + } + + @Test(expected = IllegalArgumentException.class) + public void testSetNonPositiveBandwidth() { + new DistCpOptions.Builder( + Collections.singletonList(new Path("hdfs://localhost:8020/source")), + new Path("hdfs://localhost:8020/target/")) + .withMapBandwidth(-11) + .build(); + } + + @Test(expected = IllegalArgumentException.class) + public void testSetZeroBandwidth() { + new DistCpOptions.Builder( + Collections.singletonList(new Path("hdfs://localhost:8020/source")), + new Path("hdfs://localhost:8020/target/")) + .withMapBandwidth(0) + .build(); + } + + @Test + public void testSetSkipCRC() { + final DistCpOptions.Builder builder = new DistCpOptions.Builder( + Collections.singletonList(new Path("hdfs://localhost:8020/source")), + new Path("hdfs://localhost:8020/target/")); + Assert.assertFalse(builder.build().shouldSkipCRC()); + + final DistCpOptions options = builder.withSyncFolder(true).withCRC(true) + .build(); + Assert.assertTrue(options.shouldSyncFolder()); + Assert.assertTrue(options.shouldSkipCRC()); + } + + @Test + public void testSetAtomicCommit() { + final DistCpOptions.Builder builder = new DistCpOptions.Builder( + Collections.singletonList(new Path("hdfs://localhost:8020/source")), + new Path("hdfs://localhost:8020/target/")); + Assert.assertFalse(builder.build().shouldAtomicCommit()); + + builder.withAtomicCommit(true); + Assert.assertTrue(builder.build().shouldAtomicCommit()); + + try { + builder.withSyncFolder(true).build(); + Assert.fail("Atomic and sync folders were mutually exclusive"); + } catch (IllegalArgumentException ignore) { + } + } + + @Test + public void testSetWorkPath() { + final DistCpOptions.Builder builder = new DistCpOptions.Builder( + Collections.singletonList(new Path("hdfs://localhost:8020/source")), + new Path("hdfs://localhost:8020/target/")); + Assert.assertNull(builder.build().getAtomicWorkPath()); + + builder.withAtomicCommit(true); + Assert.assertNull(builder.build().getAtomicWorkPath()); + + final Path workPath = new Path("hdfs://localhost:8020/work"); + builder.withAtomicWorkPath(workPath); + Assert.assertEquals(workPath, builder.build().getAtomicWorkPath()); + } + + @Test + public void testSetSyncFolders() { + final DistCpOptions.Builder builder = new DistCpOptions.Builder( + Collections.singletonList(new Path("hdfs://localhost:8020/source")), + new Path("hdfs://localhost:8020/target/")); + Assert.assertFalse(builder.build().shouldSyncFolder()); + + builder.withSyncFolder(true); + Assert.assertTrue(builder.build().shouldSyncFolder()); + } + + @Test + public void testSetDeleteMissing() { + final DistCpOptions.Builder builder = new DistCpOptions.Builder( + Collections.singletonList(new Path("hdfs://localhost:8020/source")), + new Path("hdfs://localhost:8020/target/")); + Assert.assertFalse(builder.build().shouldDeleteMissing()); + + DistCpOptions options = builder.withSyncFolder(true) + .withDeleteMissing(true) + .build(); + Assert.assertTrue(options.shouldSyncFolder()); + Assert.assertTrue(options.shouldDeleteMissing()); + + options = new DistCpOptions.Builder( + Collections.singletonList(new Path("hdfs://localhost:8020/source")), + new Path("hdfs://localhost:8020/target/")) + .withOverwrite(true) + .withDeleteMissing(true) + .build(); + Assert.assertTrue(options.shouldOverwrite()); + Assert.assertTrue(options.shouldDeleteMissing()); + + try { + new DistCpOptions.Builder( + Collections.singletonList(new Path("hdfs://localhost:8020/source")), + new Path("hdfs://localhost:8020/target/")) + .withDeleteMissing(true) + .build(); + fail("Delete missing should fail without update or overwrite options"); + } catch (IllegalArgumentException e) { + assertExceptionContains("Delete missing is applicable only with update " + + "or overwrite options", e); + } + try { + new DistCpOptions.Builder( + new Path("hdfs://localhost:8020/source/first"), + new Path("hdfs://localhost:8020/target/")) + .withSyncFolder(true) + .withDeleteMissing(true) + .withUseDiff("s1", "s2") + .build(); + fail("Should have failed as -delete and -diff are mutually exclusive."); + } catch (IllegalArgumentException e) { + assertExceptionContains( + "-delete and -diff/-rdiff are mutually exclusive.", e); + } + } + + @Test + public void testSetMaps() { + final DistCpOptions.Builder builder = new DistCpOptions.Builder( + Collections.singletonList(new Path("hdfs://localhost:8020/source")), + new Path("hdfs://localhost:8020/target/")); + Assert.assertEquals(DistCpConstants.DEFAULT_MAPS, + builder.build().getMaxMaps()); + + builder.maxMaps(1); + Assert.assertEquals(1, builder.build().getMaxMaps()); + + builder.maxMaps(0); + Assert.assertEquals(1, builder.build().getMaxMaps()); + } + + @Test + public void testSetNumListtatusThreads() { + final DistCpOptions.Builder builder = new DistCpOptions.Builder( + new Path("hdfs://localhost:8020/source/first"), + new Path("hdfs://localhost:8020/target/")); + // If command line argument isn't set, we expect .getNumListstatusThreads + // option to be zero (so that we know when to override conf properties). + Assert.assertEquals(0, builder.build().getNumListstatusThreads()); + + builder.withNumListstatusThreads(12); + Assert.assertEquals(12, builder.build().getNumListstatusThreads()); + + builder.withNumListstatusThreads(0); + Assert.assertEquals(0, builder.build().getNumListstatusThreads()); + + // Ignore large number of threads. + builder.withNumListstatusThreads(MAX_NUM_LISTSTATUS_THREADS * 2); + Assert.assertEquals(MAX_NUM_LISTSTATUS_THREADS, + builder.build().getNumListstatusThreads()); + } + + @Test + public void testSourceListing() { + final DistCpOptions.Builder builder = new DistCpOptions.Builder( + new Path("hdfs://localhost:8020/source/first"), + new Path("hdfs://localhost:8020/target/")); + Assert.assertEquals(new Path("hdfs://localhost:8020/source/first"), + builder.build().getSourceFileListing()); + } + + @Test(expected = IllegalArgumentException.class) + public void testMissingTarget() { + new DistCpOptions.Builder(new Path("hdfs://localhost:8020/source/first"), + null); + } + + @Test + public void testToString() { + DistCpOptions option = new DistCpOptions.Builder(new Path("abc"), + new Path("xyz")).build(); + String val = "DistCpOptions{atomicCommit=false, syncFolder=false, " + + "deleteMissing=false, ignoreFailures=false, overwrite=false, " + + "append=false, useDiff=false, useRdiff=false, " + + "fromSnapshot=null, toSnapshot=null, " + + "skipCRC=false, blocking=true, numListstatusThreads=0, maxMaps=20, " + + "mapBandwidth=0.0, copyStrategy='uniformsize', preserveStatus=[], " + + "atomicWorkPath=null, logPath=null, sourceFileListing=abc, " + + "sourcePaths=null, targetPath=xyz, filtersFile='null'," + + " blocksPerChunk=0}"; + String optionString = option.toString(); + Assert.assertEquals(val, optionString); + Assert.assertNotSame(DistCpOptionSwitch.ATOMIC_COMMIT.toString(), + DistCpOptionSwitch.ATOMIC_COMMIT.name()); + } + + @Test + public void testCopyStrategy() { + final DistCpOptions.Builder builder = new DistCpOptions.Builder( + new Path("hdfs://localhost:8020/source/first"), + new Path("hdfs://localhost:8020/target/")); + Assert.assertEquals(DistCpConstants.UNIFORMSIZE, + builder.build().getCopyStrategy()); + builder.withCopyStrategy("dynamic"); + Assert.assertEquals("dynamic", builder.build().getCopyStrategy()); + } + + @Test + public void testTargetPath() { + final DistCpOptions options = new DistCpOptions.Builder( + new Path("hdfs://localhost:8020/source/first"), + new Path("hdfs://localhost:8020/target/")).build(); + Assert.assertEquals(new Path("hdfs://localhost:8020/target/"), + options.getTargetPath()); + } + + @Test + public void testPreserve() { + DistCpOptions options = new DistCpOptions.Builder( + new Path("hdfs://localhost:8020/source/first"), + new Path("hdfs://localhost:8020/target/")) + .build(); + Assert.assertFalse(options.shouldPreserve(FileAttribute.BLOCKSIZE)); + Assert.assertFalse(options.shouldPreserve(FileAttribute.REPLICATION)); + Assert.assertFalse(options.shouldPreserve(FileAttribute.PERMISSION)); + Assert.assertFalse(options.shouldPreserve(FileAttribute.USER)); + Assert.assertFalse(options.shouldPreserve(FileAttribute.GROUP)); + Assert.assertFalse(options.shouldPreserve(FileAttribute.CHECKSUMTYPE)); + + options = new DistCpOptions.Builder( + new Path("hdfs://localhost:8020/source/first"), + new Path("hdfs://localhost:8020/target/")) + .preserve(FileAttribute.ACL) + .build(); + Assert.assertFalse(options.shouldPreserve(FileAttribute.BLOCKSIZE)); + Assert.assertFalse(options.shouldPreserve(FileAttribute.REPLICATION)); + Assert.assertFalse(options.shouldPreserve(FileAttribute.PERMISSION)); + Assert.assertFalse(options.shouldPreserve(FileAttribute.USER)); + Assert.assertFalse(options.shouldPreserve(FileAttribute.GROUP)); + Assert.assertFalse(options.shouldPreserve(FileAttribute.CHECKSUMTYPE)); + Assert.assertTrue(options.shouldPreserve(FileAttribute.ACL)); + + options = new DistCpOptions.Builder( + new Path("hdfs://localhost:8020/source/first"), + new Path("hdfs://localhost:8020/target/")) + .preserve(FileAttribute.BLOCKSIZE) + .preserve(FileAttribute.REPLICATION) + .preserve(FileAttribute.PERMISSION) + .preserve(FileAttribute.USER) + .preserve(FileAttribute.GROUP) + .preserve(FileAttribute.CHECKSUMTYPE) + .build(); + + Assert.assertTrue(options.shouldPreserve(FileAttribute.BLOCKSIZE)); + Assert.assertTrue(options.shouldPreserve(FileAttribute.REPLICATION)); + Assert.assertTrue(options.shouldPreserve(FileAttribute.PERMISSION)); + Assert.assertTrue(options.shouldPreserve(FileAttribute.USER)); + Assert.assertTrue(options.shouldPreserve(FileAttribute.GROUP)); + Assert.assertTrue(options.shouldPreserve(FileAttribute.CHECKSUMTYPE)); + Assert.assertFalse(options.shouldPreserve(FileAttribute.XATTR)); + } + + @Test + public void testAppendOption() { + final DistCpOptions.Builder builder = new DistCpOptions.Builder( + Collections.singletonList(new Path("hdfs://localhost:8020/source")), + new Path("hdfs://localhost:8020/target/")) + .withSyncFolder(true) + .withAppend(true); + Assert.assertTrue(builder.build().shouldAppend()); + + try { + // make sure -append is only valid when -update is specified + new DistCpOptions.Builder( + Collections.singletonList(new Path("hdfs://localhost:8020/source")), + new Path("hdfs://localhost:8020/target/")) + .withAppend(true) + .build(); + fail("Append should fail if update option is not specified"); + } catch (IllegalArgumentException e) { + assertExceptionContains( + "Append is valid only with update options", e); + } + + try { + // make sure -append is invalid when skipCrc is specified + new DistCpOptions.Builder( + Collections.singletonList(new Path("hdfs://localhost:8020/source")), + new Path("hdfs://localhost:8020/target/")) + .withSyncFolder(true) + .withAppend(true) + .withCRC(true) + .build(); + fail("Append should fail if skipCrc option is specified"); + } catch (IllegalArgumentException e) { + assertExceptionContains( + "Append is disallowed when skipping CRC", e); + } + } + + @Test + public void testDiffOption() { + DistCpOptions options = new DistCpOptions.Builder( + new Path("hdfs://localhost:8020/source/first"), + new Path("hdfs://localhost:8020/target/")) + .withSyncFolder(true) + .withUseDiff("s1", "s2") + .build(); + Assert.assertTrue(options.shouldUseDiff()); + Assert.assertEquals("s1", options.getFromSnapshot()); + Assert.assertEquals("s2", options.getToSnapshot()); + + options = new DistCpOptions.Builder( + new Path("hdfs://localhost:8020/source/first"), + new Path("hdfs://localhost:8020/target/")) + .withSyncFolder(true) + .withUseDiff("s1", ".") + .build(); + Assert.assertTrue(options.shouldUseDiff()); + Assert.assertEquals("s1", options.getFromSnapshot()); + Assert.assertEquals(".", options.getToSnapshot()); + + // make sure -diff is only valid when -update is specified + try { + new DistCpOptions.Builder(new Path("hdfs://localhost:8020/source/first"), + new Path("hdfs://localhost:8020/target/")) + .withUseDiff("s1", "s2") + .build(); + fail("-diff should fail if -update option is not specified"); + } catch (IllegalArgumentException e) { + assertExceptionContains( + "-diff/-rdiff is valid only with -update option", e); + } + + try { + new DistCpOptions.Builder( + new Path("hdfs://localhost:8020/source/first"), + new Path("hdfs://localhost:8020/target/")) + .withSyncFolder(true) + .withUseDiff("s1", "s2") + .withDeleteMissing(true) + .build(); + fail("Should fail as -delete and -diff/-rdiff are mutually exclusive."); + } catch (IllegalArgumentException e) { + assertExceptionContains( + "-delete and -diff/-rdiff are mutually exclusive.", e); + } + + try { + new DistCpOptions.Builder(new Path("hdfs://localhost:8020/source/first"), + new Path("hdfs://localhost:8020/target/")) + .withUseDiff("s1", "s2") + .withDeleteMissing(true) + .build(); + fail("-diff should fail if -update option is not specified"); + } catch (IllegalArgumentException e) { + assertExceptionContains( + "-delete and -diff/-rdiff are mutually exclusive.", e); + } + + try { + new DistCpOptions.Builder(new Path("hdfs://localhost:8020/source/first"), + new Path("hdfs://localhost:8020/target/")) + .withDeleteMissing(true) + .withUseDiff("s1", "s2") + .build(); + fail("Should have failed as -delete and -diff are mutually exclusive"); + } catch (IllegalArgumentException e) { + assertExceptionContains( + "-delete and -diff/-rdiff are mutually exclusive", e); + } + } + + @Test + public void testExclusionsOption() { + final DistCpOptions.Builder builder = new DistCpOptions.Builder( + new Path("hdfs://localhost:8020/source/first"), + new Path("hdfs://localhost:8020/target/")); + Assert.assertNull(builder.build().getFiltersFile()); + + builder.withFiltersFile("/tmp/filters.txt"); + Assert.assertEquals("/tmp/filters.txt", builder.build().getFiltersFile()); + } + + @Test + public void testSetOptionsForSplitLargeFile() { + final DistCpOptions.Builder builder = new DistCpOptions.Builder( + new Path("hdfs://localhost:8020/source/"), + new Path("hdfs://localhost:8020/target/")) + .withAppend(true) + .withSyncFolder(true); + Assert.assertFalse(builder.build().shouldPreserve(FileAttribute.BLOCKSIZE)); + Assert.assertTrue(builder.build().shouldAppend()); + + builder.withBlocksPerChunk(5440); + Assert.assertTrue(builder.build().shouldPreserve(FileAttribute.BLOCKSIZE)); + Assert.assertFalse(builder.build().shouldAppend()); + } + +} diff --git a/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestDistCpSync.java b/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestDistCpSync.java index 94e860412d2..717b2f08436 100644 --- a/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestDistCpSync.java +++ b/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestDistCpSync.java @@ -39,7 +39,7 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; -import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -47,7 +47,7 @@ public class TestDistCpSync { private MiniDFSCluster cluster; private final Configuration conf = new HdfsConfiguration(); private DistributedFileSystem dfs; - private DistCpOptions options; + private DistCpContext context; private final Path source = new Path("/source"); private final Path target = new Path("/target"); private final long BLOCK_SIZE = 1024; @@ -62,10 +62,13 @@ public class TestDistCpSync { dfs.mkdirs(source); dfs.mkdirs(target); - options = new DistCpOptions(Arrays.asList(source), target); - options.setSyncFolder(true); - options.setUseDiff("s1", "s2"); + final DistCpOptions options = new DistCpOptions.Builder( + Collections.singletonList(source), target) + .withSyncFolder(true) + .withUseDiff("s1", "s2") + .build(); options.appendToConf(conf); + context = new DistCpContext(options); conf.set(DistCpConstants.CONF_LABEL_TARGET_WORK_PATH, target.toString()); conf.set(DistCpConstants.CONF_LABEL_TARGET_FINAL_PATH, target.toString()); @@ -92,34 +95,34 @@ public class TestDistCpSync { // make sure the source path has been updated to the snapshot path final Path spath = new Path(source, HdfsConstants.DOT_SNAPSHOT_DIR + Path.SEPARATOR + "s2"); - Assert.assertEquals(spath, options.getSourcePaths().get(0)); + Assert.assertEquals(spath, context.getSourcePaths().get(0)); // reset source path in options - options.setSourcePaths(Arrays.asList(source)); + context.setSourcePaths(Collections.singletonList(source)); // the source/target does not have the given snapshots dfs.allowSnapshot(source); dfs.allowSnapshot(target); Assert.assertFalse(sync()); - Assert.assertEquals(spath, options.getSourcePaths().get(0)); + Assert.assertEquals(spath, context.getSourcePaths().get(0)); // reset source path in options - options.setSourcePaths(Arrays.asList(source)); + context.setSourcePaths(Collections.singletonList(source)); dfs.createSnapshot(source, "s1"); dfs.createSnapshot(source, "s2"); dfs.createSnapshot(target, "s1"); Assert.assertTrue(sync()); // reset source paths in options - options.setSourcePaths(Arrays.asList(source)); + context.setSourcePaths(Collections.singletonList(source)); // changes have been made in target final Path subTarget = new Path(target, "sub"); dfs.mkdirs(subTarget); Assert.assertFalse(sync()); // make sure the source path has been updated to the snapshot path - Assert.assertEquals(spath, options.getSourcePaths().get(0)); + Assert.assertEquals(spath, context.getSourcePaths().get(0)); // reset source paths in options - options.setSourcePaths(Arrays.asList(source)); + context.setSourcePaths(Collections.singletonList(source)); dfs.delete(subTarget, true); Assert.assertTrue(sync()); } @@ -137,7 +140,7 @@ public class TestDistCpSync { } private boolean sync() throws Exception { - DistCpSync distCpSync = new DistCpSync(options, conf); + DistCpSync distCpSync = new DistCpSync(context, conf); return distCpSync.sync(); } @@ -231,7 +234,7 @@ public class TestDistCpSync { SnapshotDiffReport report = dfs.getSnapshotDiffReport(source, "s1", "s2"); System.out.println(report); - DistCpSync distCpSync = new DistCpSync(options, conf); + DistCpSync distCpSync = new DistCpSync(context, conf); // do the sync Assert.assertTrue(distCpSync.sync()); @@ -239,24 +242,24 @@ public class TestDistCpSync { // make sure the source path has been updated to the snapshot path final Path spath = new Path(source, HdfsConstants.DOT_SNAPSHOT_DIR + Path.SEPARATOR + "s2"); - Assert.assertEquals(spath, options.getSourcePaths().get(0)); + Assert.assertEquals(spath, context.getSourcePaths().get(0)); // build copy listing final Path listingPath = new Path("/tmp/META/fileList.seq"); CopyListing listing = new SimpleCopyListing(conf, new Credentials(), distCpSync); - listing.buildListing(listingPath, options); + listing.buildListing(listingPath, context); Map copyListing = getListing(listingPath); CopyMapper copyMapper = new CopyMapper(); StubContext stubContext = new StubContext(conf, null, 0); - Mapper.Context context = + Mapper.Context mapContext = stubContext.getContext(); // Enable append - context.getConfiguration().setBoolean( + mapContext.getConfiguration().setBoolean( DistCpOptionSwitch.APPEND.getConfigLabel(), true); - copyMapper.setup(context); + copyMapper.setup(mapContext); for (Map.Entry entry : copyListing.entrySet()) { - copyMapper.map(entry.getKey(), entry.getValue(), context); + copyMapper.map(entry.getKey(), entry.getValue(), mapContext); } // verify that we only list modified and created files/directories @@ -312,7 +315,12 @@ public class TestDistCpSync { */ @Test public void testSyncWithCurrent() throws Exception { - options.setUseDiff("s1", "."); + final DistCpOptions options = new DistCpOptions.Builder( + Collections.singletonList(source), target) + .withSyncFolder(true) + .withUseDiff("s1", ".") + .build(); + context = new DistCpContext(options); initData(source); initData(target); enableAndCreateFirstSnapshot(); @@ -323,7 +331,7 @@ public class TestDistCpSync { // do the sync sync(); // make sure the source path is still unchanged - Assert.assertEquals(source, options.getSourcePaths().get(0)); + Assert.assertEquals(source, context.getSourcePaths().get(0)); } private void initData2(Path dir) throws Exception { @@ -501,32 +509,32 @@ public class TestDistCpSync { SnapshotDiffReport report = dfs.getSnapshotDiffReport(source, "s1", "s2"); System.out.println(report); - DistCpSync distCpSync = new DistCpSync(options, conf); + DistCpSync distCpSync = new DistCpSync(context, conf); // do the sync Assert.assertTrue(distCpSync.sync()); // make sure the source path has been updated to the snapshot path final Path spath = new Path(source, HdfsConstants.DOT_SNAPSHOT_DIR + Path.SEPARATOR + "s2"); - Assert.assertEquals(spath, options.getSourcePaths().get(0)); + Assert.assertEquals(spath, context.getSourcePaths().get(0)); // build copy listing final Path listingPath = new Path("/tmp/META/fileList.seq"); CopyListing listing = new SimpleCopyListing(conf, new Credentials(), distCpSync); - listing.buildListing(listingPath, options); + listing.buildListing(listingPath, context); Map copyListing = getListing(listingPath); CopyMapper copyMapper = new CopyMapper(); StubContext stubContext = new StubContext(conf, null, 0); - Mapper.Context context = + Mapper.Context mapContext = stubContext.getContext(); // Enable append - context.getConfiguration().setBoolean( + mapContext.getConfiguration().setBoolean( DistCpOptionSwitch.APPEND.getConfigLabel(), true); - copyMapper.setup(context); + copyMapper.setup(mapContext); for (Map.Entry entry : copyListing.entrySet()) { - copyMapper.map(entry.getKey(), entry.getValue(), context); + copyMapper.map(entry.getKey(), entry.getValue(), mapContext); } // verify that we only list modified and created files/directories @@ -729,7 +737,7 @@ public class TestDistCpSync { boolean threwException = false; try { - DistCpSync distCpSync = new DistCpSync(options, conf); + DistCpSync distCpSync = new DistCpSync(context, conf); // do the sync distCpSync.sync(); } catch (HadoopIllegalArgumentException e) { diff --git a/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestDistCpSyncReverseBase.java b/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestDistCpSyncReverseBase.java index fea374ee1a7..cca1c5381c4 100644 --- a/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestDistCpSyncReverseBase.java +++ b/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestDistCpSyncReverseBase.java @@ -56,7 +56,8 @@ public abstract class TestDistCpSyncReverseBase { private MiniDFSCluster cluster; private final Configuration conf = new HdfsConfiguration(); private DistributedFileSystem dfs; - private DistCpOptions options; + private DistCpOptions.Builder optionsBuilder; + private DistCpContext distCpContext; private Path source; private boolean isSrcNotSameAsTgt = true; private final Path target = new Path("/target"); @@ -139,10 +140,12 @@ public abstract class TestDistCpSyncReverseBase { } dfs.mkdirs(target); - options = new DistCpOptions(Arrays.asList(source), target); - options.setSyncFolder(true); - options.setUseRdiff("s2", "s1"); + optionsBuilder = new DistCpOptions.Builder(Arrays.asList(source), target) + .withSyncFolder(true) + .withUseRdiff("s2", "s1"); + final DistCpOptions options = optionsBuilder.build(); options.appendToConf(conf); + distCpContext = new DistCpContext(options); conf.set(DistCpConstants.CONF_LABEL_TARGET_WORK_PATH, target.toString()); conf.set(DistCpConstants.CONF_LABEL_TARGET_FINAL_PATH, target.toString()); @@ -169,33 +172,33 @@ public abstract class TestDistCpSyncReverseBase { // make sure the source path has been updated to the snapshot path final Path spath = new Path(source, HdfsConstants.DOT_SNAPSHOT_DIR + Path.SEPARATOR + "s1"); - Assert.assertEquals(spath, options.getSourcePaths().get(0)); + Assert.assertEquals(spath, distCpContext.getSourcePaths().get(0)); // reset source path in options - options.setSourcePaths(Arrays.asList(source)); + optionsBuilder.withSourcePaths(Arrays.asList(source)); // the source/target does not have the given snapshots dfs.allowSnapshot(source); dfs.allowSnapshot(target); Assert.assertFalse(sync()); - Assert.assertEquals(spath, options.getSourcePaths().get(0)); + Assert.assertEquals(spath, distCpContext.getSourcePaths().get(0)); // reset source path in options - options.setSourcePaths(Arrays.asList(source)); + optionsBuilder.withSourcePaths(Arrays.asList(source)); this.enableAndCreateFirstSnapshot(); dfs.createSnapshot(target, "s2"); Assert.assertTrue(sync()); // reset source paths in options - options.setSourcePaths(Arrays.asList(source)); + optionsBuilder.withSourcePaths(Arrays.asList(source)); // changes have been made in target final Path subTarget = new Path(target, "sub"); dfs.mkdirs(subTarget); Assert.assertFalse(sync()); // make sure the source path has been updated to the snapshot path - Assert.assertEquals(spath, options.getSourcePaths().get(0)); + Assert.assertEquals(spath, distCpContext.getSourcePaths().get(0)); // reset source paths in options - options.setSourcePaths(Arrays.asList(source)); + optionsBuilder.withSourcePaths(Arrays.asList(source)); dfs.delete(subTarget, true); Assert.assertTrue(sync()); } @@ -215,7 +218,8 @@ public abstract class TestDistCpSyncReverseBase { } private boolean sync() throws Exception { - DistCpSync distCpSync = new DistCpSync(options, conf); + distCpContext = new DistCpContext(optionsBuilder.build()); + final DistCpSync distCpSync = new DistCpSync(distCpContext, conf); return distCpSync.sync(); } @@ -328,7 +332,7 @@ public abstract class TestDistCpSyncReverseBase { SnapshotDiffReport report = dfs.getSnapshotDiffReport(target, "s2", "s1"); System.out.println(report); - DistCpSync distCpSync = new DistCpSync(options, conf); + final DistCpSync distCpSync = new DistCpSync(distCpContext, conf); lsr("Before sync target: ", shell, target); @@ -340,13 +344,13 @@ public abstract class TestDistCpSyncReverseBase { // make sure the source path has been updated to the snapshot path final Path spath = new Path(source, HdfsConstants.DOT_SNAPSHOT_DIR + Path.SEPARATOR + "s1"); - Assert.assertEquals(spath, options.getSourcePaths().get(0)); + Assert.assertEquals(spath, distCpContext.getSourcePaths().get(0)); // build copy listing final Path listingPath = new Path("/tmp/META/fileList.seq"); CopyListing listing = new SimpleCopyListing(conf, new Credentials(), distCpSync); - listing.buildListing(listingPath, options); + listing.buildListing(listingPath, distCpContext); Map copyListing = getListing(listingPath); CopyMapper copyMapper = new CopyMapper(); @@ -425,7 +429,7 @@ public abstract class TestDistCpSyncReverseBase { */ @Test public void testSyncWithCurrent() throws Exception { - options.setUseRdiff(".", "s1"); + optionsBuilder.withUseRdiff(".", "s1"); if (isSrcNotSameAsTgt) { initData(source); } @@ -440,7 +444,7 @@ public abstract class TestDistCpSyncReverseBase { final Path spath = new Path(source, HdfsConstants.DOT_SNAPSHOT_DIR + Path.SEPARATOR + "s1"); // make sure the source path is still unchanged - Assert.assertEquals(spath, options.getSourcePaths().get(0)); + Assert.assertEquals(spath, distCpContext.getSourcePaths().get(0)); } private void initData2(Path dir) throws Exception { @@ -649,7 +653,7 @@ public abstract class TestDistCpSyncReverseBase { lsrSource("Before sync source: ", shell, source); lsr("Before sync target: ", shell, target); - DistCpSync distCpSync = new DistCpSync(options, conf); + DistCpSync distCpSync = new DistCpSync(distCpContext, conf); // do the sync distCpSync.sync(); @@ -658,12 +662,12 @@ public abstract class TestDistCpSyncReverseBase { // make sure the source path has been updated to the snapshot path final Path spath = new Path(source, HdfsConstants.DOT_SNAPSHOT_DIR + Path.SEPARATOR + "s1"); - Assert.assertEquals(spath, options.getSourcePaths().get(0)); + Assert.assertEquals(spath, distCpContext.getSourcePaths().get(0)); // build copy listing final Path listingPath = new Path("/tmp/META/fileList.seq"); CopyListing listing = new SimpleCopyListing(conf, new Credentials(), distCpSync); - listing.buildListing(listingPath, options); + listing.buildListing(listingPath, distCpContext); Map copyListing = getListing(listingPath); CopyMapper copyMapper = new CopyMapper(); diff --git a/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestDistCpViewFs.java b/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestDistCpViewFs.java index 5511e094ced..d6d05421a57 100644 --- a/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestDistCpViewFs.java +++ b/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestDistCpViewFs.java @@ -413,11 +413,13 @@ public class TestDistCpViewFs { private void runTest(Path listFile, Path target, boolean targetExists, boolean sync) throws IOException { - DistCpOptions options = new DistCpOptions(listFile, target); - options.setSyncFolder(sync); - options.setTargetPathExists(targetExists); + final DistCpOptions options = new DistCpOptions.Builder(listFile, target) + .withSyncFolder(sync) + .build(); try { - new DistCp(getConf(), options).execute(); + final DistCp distcp = new DistCp(getConf(), options); + distcp.context.setTargetPathExists(targetExists); + distcp.execute(); } catch (Exception e) { LOG.error("Exception encountered ", e); throw new IOException(e); diff --git a/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestFileBasedCopyListing.java b/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestFileBasedCopyListing.java index fe2c66870e6..203de1a2adf 100644 --- a/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestFileBasedCopyListing.java +++ b/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestFileBasedCopyListing.java @@ -514,10 +514,11 @@ public class TestFileBasedCopyListing { private void runTest(Path listFile, Path target, boolean targetExists, boolean sync) throws IOException { CopyListing listing = new FileBasedCopyListing(config, CREDENTIALS); - DistCpOptions options = new DistCpOptions(listFile, target); - options.setSyncFolder(sync); - options.setTargetPathExists(targetExists); - listing.buildListing(listFile, options); + final DistCpOptions options = new DistCpOptions.Builder(listFile, target) + .withSyncFolder(sync).build(); + final DistCpContext context = new DistCpContext(options); + context.setTargetPathExists(targetExists); + listing.buildListing(listFile, context); } private void checkResult(Path listFile, int count) throws IOException { diff --git a/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestGlobbedCopyListing.java b/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestGlobbedCopyListing.java index 6c03b4ee8a8..1c92a9c5ef2 100644 --- a/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestGlobbedCopyListing.java +++ b/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestGlobbedCopyListing.java @@ -34,7 +34,7 @@ import org.junit.Test; import java.io.DataOutputStream; import java.net.URI; -import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -109,9 +109,12 @@ public class TestGlobbedCopyListing { Path source = new Path(fileSystemPath.toString() + "/tmp/source"); Path target = new Path(fileSystemPath.toString() + "/tmp/target"); Path listingPath = new Path(fileSystemPath.toString() + "/tmp/META/fileList.seq"); - DistCpOptions options = new DistCpOptions(Arrays.asList(source), target); - options.setTargetPathExists(false); - new GlobbedCopyListing(new Configuration(), CREDENTIALS).buildListing(listingPath, options); + DistCpOptions options = new DistCpOptions.Builder( + Collections.singletonList(source), target).build(); + DistCpContext context = new DistCpContext(options); + context.setTargetPathExists(false); + new GlobbedCopyListing(new Configuration(), CREDENTIALS) + .buildListing(listingPath, context); verifyContents(listingPath); } diff --git a/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestIntegration.java b/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestIntegration.java index ee8e7cc4f10..7574dedea2e 100644 --- a/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestIntegration.java +++ b/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestIntegration.java @@ -26,7 +26,6 @@ import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.mapreduce.Cluster; import org.apache.hadoop.mapreduce.JobSubmissionFiles; -import org.apache.hadoop.security.Credentials; import org.apache.hadoop.tools.util.TestDistCpUtils; import org.junit.Assert; import org.junit.BeforeClass; @@ -493,7 +492,8 @@ public class TestIntegration { List sources = new ArrayList(); sources.add(sourcePath); - DistCpOptions options = new DistCpOptions(sources, target); + DistCpOptions options = new DistCpOptions.Builder(sources, target) + .build(); Configuration conf = getConf(); Path stagingDir = JobSubmissionFiles.getStagingDir( @@ -559,14 +559,16 @@ public class TestIntegration { private void runTest(Path listFile, Path target, boolean targetExists, boolean sync, boolean delete, boolean overwrite) throws IOException { - DistCpOptions options = new DistCpOptions(listFile, target); - options.setSyncFolder(sync); - options.setDeleteMissing(delete); - options.setOverwrite(overwrite); - options.setTargetPathExists(targetExists); - options.setNumListstatusThreads(numListstatusThreads); + final DistCpOptions options = new DistCpOptions.Builder(listFile, target) + .withSyncFolder(sync) + .withDeleteMissing(delete) + .withOverwrite(overwrite) + .withNumListstatusThreads(numListstatusThreads) + .build(); try { - new DistCp(getConf(), options).execute(); + final DistCp distCp = new DistCp(getConf(), options); + distCp.context.setTargetPathExists(targetExists); + distCp.execute(); } catch (Exception e) { LOG.error("Exception encountered ", e); throw new IOException(e); diff --git a/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestOptionsParser.java b/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestOptionsParser.java index f94ba97ec34..e7fdc515a7f 100644 --- a/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestOptionsParser.java +++ b/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/TestOptionsParser.java @@ -18,7 +18,7 @@ package org.apache.hadoop.tools; -import static org.junit.Assert.assertFalse; +import static org.apache.hadoop.test.GenericTestUtils.assertExceptionContains; import static org.junit.Assert.fail; import org.junit.Assert; @@ -28,7 +28,6 @@ import org.apache.hadoop.test.GenericTestUtils; import org.apache.hadoop.tools.DistCpOptions.*; import org.apache.hadoop.conf.Configuration; -import java.util.Iterator; import java.util.NoSuchElementException; public class TestOptionsParser { @@ -329,7 +328,7 @@ public class TestOptionsParser { "100", "hdfs://localhost:9820/source/first", "hdfs://localhost:9820/target/"}); - Assert.assertEquals(DistCpOptions.maxNumListstatusThreads, + Assert.assertEquals(DistCpOptions.MAX_NUM_LISTSTATUS_THREADS, options.getNumListstatusThreads()); } @@ -382,25 +381,6 @@ public class TestOptionsParser { } catch (IllegalArgumentException ignore) {} } - @Test - public void testToString() { - DistCpOptions option = new DistCpOptions(new Path("abc"), new Path("xyz")); - String val = "DistCpOptions{atomicCommit=false, syncFolder=false, " - + "deleteMissing=false, ignoreFailures=false, overwrite=false, " - + "append=false, useDiff=false, useRdiff=false, " - + "fromSnapshot=null, toSnapshot=null, " - + "skipCRC=false, blocking=true, numListstatusThreads=0, maxMaps=20, " - + "mapBandwidth=0.0, " - + "copyStrategy='uniformsize', preserveStatus=[], " - + "preserveRawXattrs=false, atomicWorkPath=null, logPath=null, " - + "sourceFileListing=abc, sourcePaths=null, targetPath=xyz, " - + "targetPathExists=true, filtersFile='null', blocksPerChunk=0}"; - String optionString = option.toString(); - Assert.assertEquals(val, optionString); - Assert.assertNotSame(DistCpOptionSwitch.ATOMIC_COMMIT.toString(), - DistCpOptionSwitch.ATOMIC_COMMIT.name()); - } - @Test public void testCopyStrategy() { DistCpOptions options = OptionsParser.parse(new String[] { @@ -529,13 +509,8 @@ public class TestOptionsParser { "-f", "hdfs://localhost:9820/source/first", "hdfs://localhost:9820/target/"}); - int i = 0; - Iterator attribIterator = options.preserveAttributes(); - while (attribIterator.hasNext()) { - attribIterator.next(); - i++; - } - Assert.assertEquals(i, DistCpOptionSwitch.PRESERVE_STATUS_DEFAULT.length() - 2); + Assert.assertEquals(DistCpOptionSwitch.PRESERVE_STATUS_DEFAULT.length() - 2, + options.getPreserveAttributes().size()); try { OptionsParser.parse(new String[] { @@ -545,19 +520,18 @@ public class TestOptionsParser { "hdfs://localhost:9820/target"}); Assert.fail("Invalid preserve attribute"); } - catch (IllegalArgumentException ignore) {} catch (NoSuchElementException ignore) {} - options = OptionsParser.parse(new String[] { - "-f", - "hdfs://localhost:9820/source/first", - "hdfs://localhost:9820/target/"}); - Assert.assertFalse(options.shouldPreserve(FileAttribute.PERMISSION)); - options.preserve(FileAttribute.PERMISSION); - Assert.assertTrue(options.shouldPreserve(FileAttribute.PERMISSION)); + Builder builder = new DistCpOptions.Builder( + new Path("hdfs://localhost:9820/source/first"), + new Path("hdfs://localhost:9820/target/")); + Assert.assertFalse( + builder.build().shouldPreserve(FileAttribute.PERMISSION)); + builder.preserve(FileAttribute.PERMISSION); + Assert.assertTrue(builder.build().shouldPreserve(FileAttribute.PERMISSION)); - options.preserve(FileAttribute.PERMISSION); - Assert.assertTrue(options.shouldPreserve(FileAttribute.PERMISSION)); + builder.preserve(FileAttribute.PERMISSION); + Assert.assertTrue(builder.build().shouldPreserve(FileAttribute.PERMISSION)); } @Test @@ -756,28 +730,25 @@ public class TestOptionsParser { } try { - options = OptionsParser.parse(new String[] { - optionStr, "s1", "s2", "-update", "-delete", + OptionsParser.parse(new String[] { + "-diff", "s1", "s2", "-update", "-delete", "hdfs://localhost:9820/source/first", "hdfs://localhost:9820/target/" }); - assertFalse("-delete should be ignored when " - + optionStr + " is specified", - options.shouldDeleteMissing()); + fail("Should fail as -delete and -diff/-rdiff are mutually exclusive"); } catch (IllegalArgumentException e) { - fail("Got unexpected IllegalArgumentException: " + e.getMessage()); + assertExceptionContains( + "-delete and -diff/-rdiff are mutually exclusive", e); } try { - options = OptionsParser.parse(new String[] { - optionStr, "s1", "s2", "-delete", + OptionsParser.parse(new String[] { + "-diff", "s1", "s2", "-delete", "hdfs://localhost:9820/source/first", "hdfs://localhost:9820/target/" }); - fail(optionStr + " should fail if -update option is not specified"); + fail("Should fail as -delete and -diff/-rdiff are mutually exclusive"); } catch (IllegalArgumentException e) { - assertFalse("-delete should be ignored when -diff is specified", - options.shouldDeleteMissing()); - GenericTestUtils.assertExceptionContains( - "-diff/-rdiff is valid only with -update option", e); + assertExceptionContains( + "-delete and -diff/-rdiff are mutually exclusive", e); } try { @@ -785,10 +756,10 @@ public class TestOptionsParser { "-delete", "-overwrite", "hdfs://localhost:9820/source/first", "hdfs://localhost:9820/target/" }); - fail(optionStr + " should fail if -update option is not specified"); + fail("Should fail as -delete and -diff are mutually exclusive"); } catch (IllegalArgumentException e) { - GenericTestUtils.assertExceptionContains( - "-diff/-rdiff is valid only with -update option", e); + assertExceptionContains( + "-delete and -diff/-rdiff are mutually exclusive", e); } final String optionStrOther = isDiff? "-rdiff" : "-diff"; diff --git a/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/contract/AbstractContractDistCpTest.java b/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/contract/AbstractContractDistCpTest.java index 21a14d3abfc..fb1a64db184 100644 --- a/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/contract/AbstractContractDistCpTest.java +++ b/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/contract/AbstractContractDistCpTest.java @@ -19,9 +19,8 @@ package org.apache.hadoop.tools.contract; import static org.apache.hadoop.fs.contract.ContractTestUtils.*; -import static org.junit.Assert.*; -import java.util.Arrays; +import java.util.Collections; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileSystem; @@ -184,7 +183,8 @@ public abstract class AbstractContractDistCpTest * @throws Exception if there is a failure */ private void runDistCp(Path src, Path dst) throws Exception { - DistCpOptions options = new DistCpOptions(Arrays.asList(src), dst); + DistCpOptions options = new DistCpOptions.Builder( + Collections.singletonList(src), dst).build(); Job job = new DistCp(conf, options).execute(); assertNotNull("Unexpected null job returned from DistCp execution.", job); assertTrue("DistCp job did not complete.", job.isComplete()); diff --git a/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/mapred/TestCopyCommitter.java b/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/mapred/TestCopyCommitter.java index 2452d6fee3d..6ee37ccd6a5 100644 --- a/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/mapred/TestCopyCommitter.java +++ b/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/mapred/TestCopyCommitter.java @@ -32,6 +32,7 @@ import org.apache.hadoop.mapreduce.task.JobContextImpl; import org.apache.hadoop.mapreduce.lib.output.NullOutputFormat; import org.apache.hadoop.tools.CopyListing; import org.apache.hadoop.tools.DistCpConstants; +import org.apache.hadoop.tools.DistCpContext; import org.apache.hadoop.tools.DistCpOptions; import org.apache.hadoop.tools.DistCpOptions.FileAttribute; import org.apache.hadoop.tools.GlobbedCopyListing; @@ -146,15 +147,16 @@ public class TestCopyCommitter { sourceBase = TestDistCpUtils.createTestSetup(fs, sourcePerm); targetBase = TestDistCpUtils.createTestSetup(fs, initialPerm); - DistCpOptions options = new DistCpOptions(Arrays.asList(new Path(sourceBase)), - new Path("/out")); - options.preserve(FileAttribute.PERMISSION); + final DistCpOptions options = new DistCpOptions.Builder( + Collections.singletonList(new Path(sourceBase)), new Path("/out")) + .preserve(FileAttribute.PERMISSION).build(); options.appendToConf(conf); - options.setTargetPathExists(false); - + final DistCpContext context = new DistCpContext(options); + context.setTargetPathExists(false); + CopyListing listing = new GlobbedCopyListing(conf, CREDENTIALS); Path listingFile = new Path("/tmp1/" + String.valueOf(rand.nextLong())); - listing.buildListing(listingFile, options); + listing.buildListing(listingFile, context); conf.set(DistCpConstants.CONF_LABEL_TARGET_WORK_PATH, targetBase); @@ -197,15 +199,15 @@ public class TestCopyCommitter { String targetBaseAdd = TestDistCpUtils.createTestSetup(fs, FsPermission.getDefault()); fs.rename(new Path(targetBaseAdd), new Path(targetBase)); - DistCpOptions options = new DistCpOptions(Arrays.asList(new Path(sourceBase)), - new Path("/out")); - options.setSyncFolder(true); - options.setDeleteMissing(true); + final DistCpOptions options = new DistCpOptions.Builder( + Collections.singletonList(new Path(sourceBase)), new Path("/out")) + .withSyncFolder(true).withDeleteMissing(true).build(); options.appendToConf(conf); + final DistCpContext context = new DistCpContext(options); CopyListing listing = new GlobbedCopyListing(conf, CREDENTIALS); Path listingFile = new Path("/tmp1/" + String.valueOf(rand.nextLong())); - listing.buildListing(listingFile, options); + listing.buildListing(listingFile, context); conf.set(DistCpConstants.CONF_LABEL_TARGET_WORK_PATH, targetBase); conf.set(DistCpConstants.CONF_LABEL_TARGET_FINAL_PATH, targetBase); @@ -266,15 +268,15 @@ public class TestCopyCommitter { TestDistCpUtils.createFile(fs, targetBase + "/9"); TestDistCpUtils.createFile(fs, targetBase + "/A"); - DistCpOptions options = new DistCpOptions(Arrays.asList(new Path(sourceBase)), - new Path("/out")); - options.setSyncFolder(true); - options.setDeleteMissing(true); + final DistCpOptions options = new DistCpOptions.Builder( + Collections.singletonList(new Path(sourceBase)), new Path("/out")) + .withSyncFolder(true).withDeleteMissing(true).build(); options.appendToConf(conf); + final DistCpContext context = new DistCpContext(options); CopyListing listing = new GlobbedCopyListing(conf, CREDENTIALS); Path listingFile = new Path("/tmp1/" + String.valueOf(rand.nextLong())); - listing.buildListing(listingFile, options); + listing.buildListing(listingFile, context); conf.set(DistCpConstants.CONF_LABEL_TARGET_WORK_PATH, targetBase); conf.set(DistCpConstants.CONF_LABEL_TARGET_FINAL_PATH, targetBase); diff --git a/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/mapred/TestUniformSizeInputFormat.java b/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/mapred/TestUniformSizeInputFormat.java index 78e226252d2..5315137fde5 100644 --- a/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/mapred/TestUniformSizeInputFormat.java +++ b/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/mapred/TestUniformSizeInputFormat.java @@ -31,6 +31,7 @@ import org.apache.hadoop.mapreduce.task.JobContextImpl; import org.apache.hadoop.mapreduce.lib.input.FileSplit; import org.apache.hadoop.tools.CopyListing; import org.apache.hadoop.tools.CopyListingFileStatus; +import org.apache.hadoop.tools.DistCpContext; import org.apache.hadoop.tools.DistCpOptions; import org.apache.hadoop.tools.StubContext; import org.apache.hadoop.security.Credentials; @@ -74,9 +75,9 @@ public class TestUniformSizeInputFormat { List sourceList = new ArrayList(); sourceList.add(sourcePath); - final DistCpOptions distCpOptions = new DistCpOptions(sourceList, targetPath); - distCpOptions.setMaxMaps(nMaps); - return distCpOptions; + return new DistCpOptions.Builder(sourceList, targetPath) + .maxMaps(nMaps) + .build(); } private static int createFile(String path, int fileSize) throws Exception { @@ -100,14 +101,14 @@ public class TestUniformSizeInputFormat { } public void testGetSplits(int nMaps) throws Exception { - DistCpOptions options = getOptions(nMaps); + DistCpContext context = new DistCpContext(getOptions(nMaps)); Configuration configuration = new Configuration(); configuration.set("mapred.map.tasks", - String.valueOf(options.getMaxMaps())); + String.valueOf(context.getMaxMaps())); Path listFile = new Path(cluster.getFileSystem().getUri().toString() + "/tmp/testGetSplits_1/fileList.seq"); - CopyListing.getCopyListing(configuration, CREDENTIALS, options). - buildListing(listFile, options); + CopyListing.getCopyListing(configuration, CREDENTIALS, context) + .buildListing(listFile, context); JobContext jobContext = new JobContextImpl(configuration, new JobID()); UniformSizeInputFormat uniformSizeInputFormat = new UniformSizeInputFormat(); diff --git a/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/mapred/lib/TestDynamicInputFormat.java b/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/mapred/lib/TestDynamicInputFormat.java index bb2dd9d37d1..87290caa8a9 100644 --- a/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/mapred/lib/TestDynamicInputFormat.java +++ b/hadoop-tools/hadoop-distcp/src/test/java/org/apache/hadoop/tools/mapred/lib/TestDynamicInputFormat.java @@ -19,6 +19,7 @@ package org.apache.hadoop.tools.mapred.lib; import org.apache.hadoop.tools.DistCpConstants; +import org.apache.hadoop.tools.DistCpContext; import org.junit.Assert; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -84,9 +85,9 @@ public class TestDynamicInputFormat { List sourceList = new ArrayList(); sourceList.add(sourcePath); - DistCpOptions options = new DistCpOptions(sourceList, targetPath); - options.setMaxMaps(NUM_SPLITS); - return options; + return new DistCpOptions.Builder(sourceList, targetPath) + .maxMaps(NUM_SPLITS) + .build(); } private static void createFile(String path) throws Exception { @@ -110,13 +111,13 @@ public class TestDynamicInputFormat { @Test public void testGetSplits() throws Exception { - DistCpOptions options = getOptions(); + final DistCpContext context = new DistCpContext(getOptions()); Configuration configuration = new Configuration(); configuration.set("mapred.map.tasks", - String.valueOf(options.getMaxMaps())); - CopyListing.getCopyListing(configuration, CREDENTIALS, options).buildListing( - new Path(cluster.getFileSystem().getUri().toString() - +"/tmp/testDynInputFormat/fileList.seq"), options); + String.valueOf(context.getMaxMaps())); + CopyListing.getCopyListing(configuration, CREDENTIALS, context) + .buildListing(new Path(cluster.getFileSystem().getUri().toString() + +"/tmp/testDynInputFormat/fileList.seq"), context); JobContext jobContext = new JobContextImpl(configuration, new JobID()); DynamicInputFormat inputFormat = From b8838578ce1f1ae1f93cc6b40cc98b58321ad9ee Mon Sep 17 00:00:00 2001 From: Varun Saxena Date: Sun, 2 Apr 2017 04:37:34 +0530 Subject: [PATCH 180/188] YARN-6414. ATSv2 HBase related tests fail due to guava version upgrade (Haibo Chen via Varun Saxena) --- hadoop-project/pom.xml | 1 + .../pom.xml | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/hadoop-project/pom.xml b/hadoop-project/pom.xml index e55308fed19..f327933b40b 100644 --- a/hadoop-project/pom.xml +++ b/hadoop-project/pom.xml @@ -51,6 +51,7 @@ 0.8.2.1 1.2.4 2.5.1 + 11.0.2 ${project.version} 1.0.13 diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice-hbase-tests/pom.xml b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice-hbase-tests/pom.xml index d44aa22a38d..afe440fb851 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice-hbase-tests/pom.xml +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice-hbase-tests/pom.xml @@ -141,6 +141,7 @@ com.google.guava guava + ${hbase-compatible-guava.version} test @@ -369,6 +370,24 @@ + + + org.apache.maven.plugins + maven-enforcer-plugin + + + depcheck + + + true + + + enforce + + + + + From a4b5aa8493e0bd9006f44291d265c28ab86497e1 Mon Sep 17 00:00:00 2001 From: Varun Saxena Date: Sun, 2 Apr 2017 04:54:12 +0530 Subject: [PATCH 181/188] YARN-6377. NMTimelinePublisher#serviceStop does not stop timeline clients (Haibo Chen via Varun Saxena) --- .../timelineservice/NMTimelinePublisher.java | 8 ++++++++ .../timelineservice/TestNMTimelinePublisher.java | 10 +++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/timelineservice/NMTimelinePublisher.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/timelineservice/NMTimelinePublisher.java index ce2c6561a2b..8aaae799372 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/timelineservice/NMTimelinePublisher.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/timelineservice/NMTimelinePublisher.java @@ -101,6 +101,14 @@ public class NMTimelinePublisher extends CompositeService { this.nodeId = context.getNodeId(); } + @Override + protected void serviceStop() throws Exception { + for(ApplicationId app : appToClientMap.keySet()) { + stopTimelineClient(app); + } + super.serviceStop(); + } + @VisibleForTesting Map getAppToClientMap() { return appToClientMap; diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/timelineservice/TestNMTimelinePublisher.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/timelineservice/TestNMTimelinePublisher.java index e1161229317..0b8eaa97563 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/timelineservice/TestNMTimelinePublisher.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/timelineservice/TestNMTimelinePublisher.java @@ -35,6 +35,7 @@ import org.apache.hadoop.yarn.api.records.NodeId; import org.apache.hadoop.yarn.api.records.timelineservice.TimelineEntity; import org.apache.hadoop.yarn.api.records.timelineservice.TimelineMetric; import org.apache.hadoop.yarn.client.api.impl.TimelineV2ClientImpl; +import org.apache.hadoop.yarn.conf.YarnConfiguration; import org.apache.hadoop.yarn.exceptions.YarnException; import org.apache.hadoop.yarn.server.nodemanager.Context; import org.apache.hadoop.yarn.server.nodemanager.containermanager.container.Container; @@ -53,14 +54,21 @@ public class TestNMTimelinePublisher { final DummyTimelineClient timelineClient = new DummyTimelineClient(null); when(context.getNodeId()).thenReturn(NodeId.newInstance("localhost", 0)); when(context.getHttpPort()).thenReturn(0); + + Configuration conf = new Configuration(); + conf.setBoolean(YarnConfiguration.TIMELINE_SERVICE_ENABLED, true); + conf.setFloat(YarnConfiguration.TIMELINE_SERVICE_VERSION, 2.0f); + NMTimelinePublisher publisher = new NMTimelinePublisher(context) { public void createTimelineClient(ApplicationId appId) { if (!getAppToClientMap().containsKey(appId)) { + timelineClient.init(getConfig()); + timelineClient.start(); getAppToClientMap().put(appId, timelineClient); } } }; - publisher.init(new Configuration()); + publisher.init(conf); publisher.start(); ApplicationId appId = ApplicationId.newInstance(0, 1); publisher.createTimelineClient(appId); From 845529b3ab338e759665a687eb525fb2cccde7bf Mon Sep 17 00:00:00 2001 From: Akira Ajisaka Date: Mon, 3 Apr 2017 13:06:24 +0900 Subject: [PATCH 182/188] MAPREDUCE-6824. TaskAttemptImpl#createCommonContainerLaunchContext is longer than 150 lines. Contributed by Chris Trezzo. --- .../v2/app/job/impl/TaskAttemptImpl.java | 279 ++++++++++-------- 1 file changed, 150 insertions(+), 129 deletions(-) diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/main/java/org/apache/hadoop/mapreduce/v2/app/job/impl/TaskAttemptImpl.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/main/java/org/apache/hadoop/mapreduce/v2/app/job/impl/TaskAttemptImpl.java index 43058240707..9ea1b9aa922 100755 --- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/main/java/org/apache/hadoop/mapreduce/v2/app/job/impl/TaskAttemptImpl.java +++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/main/java/org/apache/hadoop/mapreduce/v2/app/job/impl/TaskAttemptImpl.java @@ -755,7 +755,7 @@ public abstract class TaskAttemptImpl implements new HashMap(); // Application environment - Map environment = new HashMap(); + Map environment; // Service data Map serviceData = new HashMap(); @@ -763,157 +763,178 @@ public abstract class TaskAttemptImpl implements // Tokens ByteBuffer taskCredentialsBuffer = ByteBuffer.wrap(new byte[]{}); try { - FileSystem remoteFS = FileSystem.get(conf); - // //////////// Set up JobJar to be localized properly on the remote NM. - String jobJar = conf.get(MRJobConfig.JAR); - if (jobJar != null) { - final Path jobJarPath = new Path(jobJar); - final FileSystem jobJarFs = FileSystem.get(jobJarPath.toUri(), conf); - Path remoteJobJar = jobJarPath.makeQualified(jobJarFs.getUri(), - jobJarFs.getWorkingDirectory()); - LocalResource rc = createLocalResource(jobJarFs, remoteJobJar, - LocalResourceType.PATTERN, LocalResourceVisibility.APPLICATION); - String pattern = conf.getPattern(JobContext.JAR_UNPACK_PATTERN, - JobConf.UNPACK_JAR_PATTERN_DEFAULT).pattern(); - rc.setPattern(pattern); - localResources.put(MRJobConfig.JOB_JAR, rc); - LOG.info("The job-jar file on the remote FS is " - + remoteJobJar.toUri().toASCIIString()); - } else { - // Job jar may be null. For e.g, for pipes, the job jar is the hadoop - // mapreduce jar itself which is already on the classpath. - LOG.info("Job jar is not present. " - + "Not adding any jar to the list of resources."); - } - // //////////// End of JobJar setup + configureJobJar(conf, localResources); - // //////////// Set up JobConf to be localized properly on the remote NM. - Path path = - MRApps.getStagingAreaDir(conf, UserGroupInformation - .getCurrentUser().getShortUserName()); - Path remoteJobSubmitDir = - new Path(path, oldJobId.toString()); - Path remoteJobConfPath = - new Path(remoteJobSubmitDir, MRJobConfig.JOB_CONF_FILE); - localResources.put( - MRJobConfig.JOB_CONF_FILE, - createLocalResource(remoteFS, remoteJobConfPath, - LocalResourceType.FILE, LocalResourceVisibility.APPLICATION)); - LOG.info("The job-conf file on the remote FS is " - + remoteJobConfPath.toUri().toASCIIString()); - // //////////// End of JobConf setup + configureJobConf(conf, localResources, oldJobId); // Setup DistributedCache MRApps.setupDistributedCache(conf, localResources); - // Setup up task credentials buffer - LOG.info("Adding #" + credentials.numberOfTokens() - + " tokens and #" + credentials.numberOfSecretKeys() - + " secret keys for NM use for launching container"); - Credentials taskCredentials = new Credentials(credentials); - - // LocalStorageToken is needed irrespective of whether security is enabled - // or not. - TokenCache.setJobToken(jobToken, taskCredentials); - - DataOutputBuffer containerTokens_dob = new DataOutputBuffer(); - LOG.info("Size of containertokens_dob is " - + taskCredentials.numberOfTokens()); - taskCredentials.writeTokenStorageToStream(containerTokens_dob); taskCredentialsBuffer = - ByteBuffer.wrap(containerTokens_dob.getData(), 0, - containerTokens_dob.getLength()); + configureTokens(jobToken, credentials, serviceData); - // Add shuffle secret key - // The secret key is converted to a JobToken to preserve backwards - // compatibility with an older ShuffleHandler running on an NM. - LOG.info("Putting shuffle token in serviceData"); - byte[] shuffleSecret = TokenCache.getShuffleSecretKey(credentials); - if (shuffleSecret == null) { - LOG.warn("Cannot locate shuffle secret in credentials." - + " Using job token as shuffle secret."); - shuffleSecret = jobToken.getPassword(); - } - Token shuffleToken = new Token( - jobToken.getIdentifier(), shuffleSecret, jobToken.getKind(), - jobToken.getService()); - serviceData.put(ShuffleHandler.MAPREDUCE_SHUFFLE_SERVICEID, - ShuffleHandler.serializeServiceData(shuffleToken)); + addExternalShuffleProviders(conf, serviceData); - // add external shuffle-providers - if any - Collection shuffleProviders = conf.getStringCollection( - MRJobConfig.MAPREDUCE_JOB_SHUFFLE_PROVIDER_SERVICES); - if (! shuffleProviders.isEmpty()) { - Collection auxNames = conf.getStringCollection( - YarnConfiguration.NM_AUX_SERVICES); + environment = configureEnv(conf); - for (final String shuffleProvider : shuffleProviders) { - if (shuffleProvider.equals(ShuffleHandler.MAPREDUCE_SHUFFLE_SERVICEID)) { - continue; // skip built-in shuffle-provider that was already inserted with shuffle secret key - } - if (auxNames.contains(shuffleProvider)) { - LOG.info("Adding ShuffleProvider Service: " + shuffleProvider + " to serviceData"); - // This only serves for INIT_APP notifications - // The shuffle service needs to be able to work with the host:port information provided by the AM - // (i.e. shuffle services which require custom location / other configuration are not supported) - serviceData.put(shuffleProvider, ByteBuffer.allocate(0)); - } - else { - throw new YarnRuntimeException("ShuffleProvider Service: " + shuffleProvider + - " was NOT found in the list of aux-services that are available in this NM." + - " You may need to specify this ShuffleProvider as an aux-service in your yarn-site.xml"); - } - } - } - - MRApps.addToEnvironment( - environment, - Environment.CLASSPATH.name(), - getInitialClasspath(conf), conf); - - if (initialAppClasspath != null) { - MRApps.addToEnvironment( - environment, - Environment.APP_CLASSPATH.name(), - initialAppClasspath, conf); - } } catch (IOException e) { throw new YarnRuntimeException(e); } - // Shell - environment.put( - Environment.SHELL.name(), - conf.get( - MRJobConfig.MAPRED_ADMIN_USER_SHELL, - MRJobConfig.DEFAULT_SHELL) - ); - - // Add pwd to LD_LIBRARY_PATH, add this before adding anything else - MRApps.addToEnvironment( - environment, - Environment.LD_LIBRARY_PATH.name(), - MRApps.crossPlatformifyMREnv(conf, Environment.PWD), conf); - - // Add the env variables passed by the admin - MRApps.setEnvFromInputString( - environment, - conf.get( - MRJobConfig.MAPRED_ADMIN_USER_ENV, - MRJobConfig.DEFAULT_MAPRED_ADMIN_USER_ENV), conf - ); - // Construct the actual Container // The null fields are per-container and will be constructed for each // container separately. ContainerLaunchContext container = ContainerLaunchContext.newInstance(localResources, environment, null, - serviceData, taskCredentialsBuffer, applicationACLs); + serviceData, taskCredentialsBuffer, applicationACLs); return container; } + private static Map configureEnv(Configuration conf) + throws IOException { + Map environment = new HashMap(); + MRApps.addToEnvironment(environment, Environment.CLASSPATH.name(), + getInitialClasspath(conf), conf); + + if (initialAppClasspath != null) { + MRApps.addToEnvironment(environment, Environment.APP_CLASSPATH.name(), + initialAppClasspath, conf); + } + + // Shell + environment.put(Environment.SHELL.name(), conf + .get(MRJobConfig.MAPRED_ADMIN_USER_SHELL, MRJobConfig.DEFAULT_SHELL)); + + // Add pwd to LD_LIBRARY_PATH, add this before adding anything else + MRApps.addToEnvironment(environment, Environment.LD_LIBRARY_PATH.name(), + MRApps.crossPlatformifyMREnv(conf, Environment.PWD), conf); + + // Add the env variables passed by the admin + MRApps.setEnvFromInputString(environment, + conf.get(MRJobConfig.MAPRED_ADMIN_USER_ENV, + MRJobConfig.DEFAULT_MAPRED_ADMIN_USER_ENV), + conf); + return environment; + } + + private static void configureJobJar(Configuration conf, + Map localResources) throws IOException { + // Set up JobJar to be localized properly on the remote NM. + String jobJar = conf.get(MRJobConfig.JAR); + if (jobJar != null) { + final Path jobJarPath = new Path(jobJar); + final FileSystem jobJarFs = FileSystem.get(jobJarPath.toUri(), conf); + Path remoteJobJar = jobJarPath.makeQualified(jobJarFs.getUri(), + jobJarFs.getWorkingDirectory()); + LocalResource rc = createLocalResource(jobJarFs, remoteJobJar, + LocalResourceType.PATTERN, LocalResourceVisibility.APPLICATION); + String pattern = conf.getPattern(JobContext.JAR_UNPACK_PATTERN, + JobConf.UNPACK_JAR_PATTERN_DEFAULT).pattern(); + rc.setPattern(pattern); + localResources.put(MRJobConfig.JOB_JAR, rc); + LOG.info("The job-jar file on the remote FS is " + + remoteJobJar.toUri().toASCIIString()); + } else { + // Job jar may be null. For e.g, for pipes, the job jar is the hadoop + // mapreduce jar itself which is already on the classpath. + LOG.info("Job jar is not present. " + + "Not adding any jar to the list of resources."); + } + } + + private static void configureJobConf(Configuration conf, + Map localResources, + final org.apache.hadoop.mapred.JobID oldJobId) throws IOException { + // Set up JobConf to be localized properly on the remote NM. + Path path = MRApps.getStagingAreaDir(conf, + UserGroupInformation.getCurrentUser().getShortUserName()); + Path remoteJobSubmitDir = new Path(path, oldJobId.toString()); + Path remoteJobConfPath = + new Path(remoteJobSubmitDir, MRJobConfig.JOB_CONF_FILE); + FileSystem remoteFS = FileSystem.get(conf); + localResources.put(MRJobConfig.JOB_CONF_FILE, + createLocalResource(remoteFS, remoteJobConfPath, LocalResourceType.FILE, + LocalResourceVisibility.APPLICATION)); + LOG.info("The job-conf file on the remote FS is " + + remoteJobConfPath.toUri().toASCIIString()); + } + + private static ByteBuffer configureTokens(Token jobToken, + Credentials credentials, + Map serviceData) throws IOException { + // Setup up task credentials buffer + LOG.info("Adding #" + credentials.numberOfTokens() + " tokens and #" + + credentials.numberOfSecretKeys() + + " secret keys for NM use for launching container"); + Credentials taskCredentials = new Credentials(credentials); + + // LocalStorageToken is needed irrespective of whether security is enabled + // or not. + TokenCache.setJobToken(jobToken, taskCredentials); + + DataOutputBuffer containerTokens_dob = new DataOutputBuffer(); + LOG.info( + "Size of containertokens_dob is " + taskCredentials.numberOfTokens()); + taskCredentials.writeTokenStorageToStream(containerTokens_dob); + ByteBuffer taskCredentialsBuffer = + ByteBuffer.wrap(containerTokens_dob.getData(), 0, + containerTokens_dob.getLength()); + + // Add shuffle secret key + // The secret key is converted to a JobToken to preserve backwards + // compatibility with an older ShuffleHandler running on an NM. + LOG.info("Putting shuffle token in serviceData"); + byte[] shuffleSecret = TokenCache.getShuffleSecretKey(credentials); + if (shuffleSecret == null) { + LOG.warn("Cannot locate shuffle secret in credentials." + + " Using job token as shuffle secret."); + shuffleSecret = jobToken.getPassword(); + } + Token shuffleToken = + new Token(jobToken.getIdentifier(), shuffleSecret, + jobToken.getKind(), jobToken.getService()); + serviceData.put(ShuffleHandler.MAPREDUCE_SHUFFLE_SERVICEID, + ShuffleHandler.serializeServiceData(shuffleToken)); + return taskCredentialsBuffer; + } + + private static void addExternalShuffleProviders(Configuration conf, + Map serviceData) { + // add external shuffle-providers - if any + Collection shuffleProviders = conf.getStringCollection( + MRJobConfig.MAPREDUCE_JOB_SHUFFLE_PROVIDER_SERVICES); + if (!shuffleProviders.isEmpty()) { + Collection auxNames = + conf.getStringCollection(YarnConfiguration.NM_AUX_SERVICES); + + for (final String shuffleProvider : shuffleProviders) { + if (shuffleProvider + .equals(ShuffleHandler.MAPREDUCE_SHUFFLE_SERVICEID)) { + continue; // skip built-in shuffle-provider that was already inserted + // with shuffle secret key + } + if (auxNames.contains(shuffleProvider)) { + LOG.info("Adding ShuffleProvider Service: " + shuffleProvider + + " to serviceData"); + // This only serves for INIT_APP notifications + // The shuffle service needs to be able to work with the host:port + // information provided by the AM + // (i.e. shuffle services which require custom location / other + // configuration are not supported) + serviceData.put(shuffleProvider, ByteBuffer.allocate(0)); + } else { + throw new YarnRuntimeException("ShuffleProvider Service: " + + shuffleProvider + + " was NOT found in the list of aux-services that are " + + "available in this NM. You may need to specify this " + + "ShuffleProvider as an aux-service in your yarn-site.xml"); + } + } + } + } + static ContainerLaunchContext createContainerLaunchContext( Map applicationACLs, Configuration conf, Token jobToken, Task remoteTask, From bc7aff7cec07bbc3fed63a00c8f1584c34670998 Mon Sep 17 00:00:00 2001 From: Wei-Chiu Chuang Date: Mon, 3 Apr 2017 07:32:27 -0700 Subject: [PATCH 183/188] HDFS-11515. -du throws ConcurrentModificationException. Contributed by Istvan Fajth, Wei-Chiu Chuang. --- .../DirectoryWithSnapshotFeature.java | 5 ++ .../snapshot/TestRenameWithSnapshots.java | 6 +- .../snapshot/TestSnapshotDeletion.java | 75 +++++++++++++++++++ 3 files changed, 84 insertions(+), 2 deletions(-) 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 9addbfa7ace..984067990ca 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 @@ -633,6 +633,11 @@ public class DirectoryWithSnapshotFeature implements INode.Feature { for(DirectoryDiff d : diffs) { for(INode deletedNode : d.getChildrenDiff().getList(ListType.DELETED)) { context.reportDeletedSnapshottedNode(deletedNode); + if (deletedNode.isDirectory()){ + DirectoryWithSnapshotFeature sf = + deletedNode.asDirectory().getDirectoryWithSnapshotFeature(); + sf.computeContentSummary4Snapshot(context); + } } } } 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 d1b3aa6f870..d06c384fa2f 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 @@ -26,6 +26,7 @@ import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyObject; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; +import static org.apache.hadoop.test.GenericTestUtils.getTestDir; import java.io.File; import java.io.IOException; @@ -2429,7 +2430,7 @@ public class TestRenameWithSnapshots { */ @Test (timeout=300000) public void testDu() throws Exception { - File tempFile = File.createTempFile("testDu-", ".tmp"); + File tempFile = File.createTempFile("testDu-", ".tmp", getTestDir()); tempFile.deleteOnExit(); final FileSystem localfs = FileSystem.getLocal(conf); @@ -2539,7 +2540,8 @@ public class TestRenameWithSnapshots { */ @Test (timeout=300000) public void testDuMultipleDirs() throws Exception { - File tempFile = File.createTempFile("testDuMultipleDirs-", "" + ".tmp"); + File tempFile = File.createTempFile("testDuMultipleDirs-", ".tmp", + getTestDir()); tempFile.deleteOnExit(); final FileSystem localfs = FileSystem.getLocal(conf); diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/snapshot/TestSnapshotDeletion.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/snapshot/TestSnapshotDeletion.java index ca53788e98d..7926e44d0cf 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/snapshot/TestSnapshotDeletion.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/snapshot/TestSnapshotDeletion.java @@ -27,6 +27,7 @@ import java.io.PrintStream; import java.security.PrivilegedAction; import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.ContentSummary; import org.apache.hadoop.fs.FileStatus; import org.apache.hadoop.fs.FsShell; import org.apache.hadoop.fs.Path; @@ -1232,4 +1233,78 @@ public class TestSnapshotDeletion { // make sure bar has been cleaned from inodeMap Assert.assertNull(fsdir.getInode(fileId)); } + + /** + * Test for HDFS-11515. + * In a scenario where a directory with subdirectories is removed from the + * file system after taking a snapshot on one of its ancestors, du command + * fails with a ConcurrentModificationException until a new snapshot is taken, + * or the old snapshots are removed. + * This test is testing this scenario with checks on the space consumed + * calculation. + * + * @throws Exception + */ + @Test(timeout = 180000) + public void testDuWithRmdirInSnapshots() throws Exception { + final Path parent = new Path("/testDuWithRmdirInSnapshots"); + final Path snapshotDir = new Path(parent, "snapshotDir"); + final Path dir1 = new Path(snapshotDir, "d1"); //snapshotDir/d1 + final Path dir2 = new Path(snapshotDir, "d2"); //snapshotDir/d2 + final Path dir4 = new Path(dir2, "d4"); //snapshotDir/d2/d4 + final Path dir3 = new Path(snapshotDir, "d3"); //snapshotDir/d3 + final Path dir5 = new Path(dir3, "d5"); //snapshotDir/d3/d5 + final Path aFileOutsideSnapshots = new Path(parent, "aFile"); + final Path aFileInsideSnapshots = new Path(dir5, "aFile"); + + final String snapshotName = "s1"; + final String snapshotName2 = "s2"; + + final long spaceConsumed = BLOCKSIZE * REPLICATION; + final long spaceConsumed2 = 2 * spaceConsumed; + ContentSummary summary = null; + + DFSTestUtil.createFile(hdfs, aFileOutsideSnapshots, + BLOCKSIZE, REPLICATION, 0); + summary = hdfs.getContentSummary(parent); + assertEquals("Du is wrong even with one file without further ado.", + spaceConsumed, summary.getSpaceConsumed()); + + hdfs.mkdirs(snapshotDir); + hdfs.allowSnapshot(snapshotDir); + hdfs.mkdirs(dir1); + + hdfs.createSnapshot(snapshotDir, snapshotName); + + hdfs.mkdirs(dir4); + hdfs.mkdirs(dir5); + DFSTestUtil.createFile(hdfs, aFileInsideSnapshots, + BLOCKSIZE, REPLICATION, 0); + summary = hdfs.getContentSummary(parent); + assertEquals("Du is wrong with 2 files added to the file system.", + spaceConsumed2, summary.getSpaceConsumed()); + + hdfs.createSnapshot(snapshotDir, snapshotName2); + + hdfs.delete(dir2, true); + hdfs.delete(dir3, true); + + summary = hdfs.getContentSummary(parent); + assertEquals("Snapshot file count is not matching expected value.", + 1, summary.getSnapshotFileCount()); + assertEquals("Snapshot directory count is not matching expected value.", + 4, summary.getSnapshotDirectoryCount()); + assertEquals("Consumed space does not matching expected value.", + spaceConsumed, summary.getSnapshotSpaceConsumed()); + assertEquals("Snapshot length is not matching expected value.", + BLOCKSIZE, summary.getSnapshotLength()); + assertEquals("File count is not matching expected value.", + 2, summary.getFileCount()); + assertEquals("Directory count is not matching expected value.", + 7, summary.getDirectoryCount()); + assertEquals("Consumed space is not matching expected value.", + spaceConsumed2, summary.getSpaceConsumed()); + assertEquals("Length is not matching expected value.", + 2 * BLOCKSIZE, summary.getLength()); + } } From bbd68478d5743b3b2911bf3febed7daa89479e45 Mon Sep 17 00:00:00 2001 From: Wei-Chiu Chuang Date: Mon, 3 Apr 2017 07:57:28 -0700 Subject: [PATCH 184/188] HDFS-11598. Improve -setrep for Erasure Coded files. Contributed by Yiqun Lin. --- .../hadoop/fs/shell/SetReplication.java | 17 +++++-- .../hadoop/hdfs/TestSetrepIncreasing.java | 44 +++++++++++++++++++ 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/shell/SetReplication.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/shell/SetReplication.java index 2231c58f2b5..16e6e929ede 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/shell/SetReplication.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/shell/SetReplication.java @@ -85,11 +85,20 @@ class SetReplication extends FsCommand { } if (item.stat.isFile()) { - if (!item.fs.setReplication(item.path, newRep)) { - throw new IOException("Could not set replication for: " + item); + // Do the checking if the file is erasure coded since + // replication factor for an EC file is meaningless. + if (!item.stat.isErasureCoded()) { + if (!item.fs.setReplication(item.path, newRep)) { + throw new IOException("Could not set replication for: " + item); + } + out.println("Replication " + newRep + " set: " + item); + if (waitOpt) { + waitList.add(item); + } + } else { + out.println("Did not set replication for: " + item + + ", because it's an erasure coded file."); } - out.println("Replication " + newRep + " set: " + item); - if (waitOpt) waitList.add(item); } } diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestSetrepIncreasing.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestSetrepIncreasing.java index fee30b5e58b..50d7b2756f9 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestSetrepIncreasing.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestSetrepIncreasing.java @@ -20,7 +20,9 @@ package org.apache.hadoop.hdfs; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.PrintStream; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.BlockLocation; @@ -28,6 +30,7 @@ import org.apache.hadoop.fs.FileStatus; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.FsShell; import org.apache.hadoop.fs.Path; +import org.apache.hadoop.hdfs.protocol.ClientProtocol; import org.apache.hadoop.hdfs.server.datanode.SimulatedFSDataset; import org.junit.Test; @@ -102,4 +105,45 @@ public class TestSetrepIncreasing { cluster.shutdown(); } } + + @Test + public void testSetRepOnECFile() throws Exception { + ClientProtocol client; + Configuration conf = new HdfsConfiguration(); + conf.set(DFSConfigKeys.DFS_NAMENODE_EC_POLICIES_ENABLED_KEY, + StripedFileTestUtil.getDefaultECPolicy().getName()); + MiniDFSCluster cluster = new MiniDFSCluster.Builder(conf).numDataNodes(1) + .build(); + cluster.waitActive(); + client = NameNodeProxies.createProxy(conf, + cluster.getFileSystem(0).getUri(), ClientProtocol.class).getProxy(); + client.setErasureCodingPolicy("/", + StripedFileTestUtil.getDefaultECPolicy().getName()); + + FileSystem dfs = cluster.getFileSystem(); + try { + Path d = new Path("/tmp"); + dfs.mkdirs(d); + Path f = new Path(d, "foo"); + dfs.createNewFile(f); + FileStatus file = dfs.getFileStatus(f); + assertTrue(file.isErasureCoded()); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + System.setOut(new PrintStream(out)); + String[] args = {"-setrep", "2", "" + f}; + FsShell shell = new FsShell(); + shell.setConf(conf); + assertEquals(0, shell.run(args)); + assertTrue( + out.toString().contains("Did not set replication for: /tmp/foo")); + + // verify the replication factor of the EC file + file = dfs.getFileStatus(f); + assertEquals(1, file.getReplication()); + } finally { + dfs.close(); + cluster.shutdown(); + } + } } From 5faa949b782be48ef400d2eb1695f420455de764 Mon Sep 17 00:00:00 2001 From: Mingliang Liu Date: Mon, 3 Apr 2017 11:07:14 -0700 Subject: [PATCH 185/188] HADOOP-14268. Fix markdown itemization in hadoop-aws documents. Contributed by Akira Ajisaka --- .../hadoop-aws/src/site/markdown/tools/hadoop-aws/index.md | 2 ++ .../hadoop-aws/src/site/markdown/tools/hadoop-aws/testing.md | 1 + 2 files changed, 3 insertions(+) diff --git a/hadoop-tools/hadoop-aws/src/site/markdown/tools/hadoop-aws/index.md b/hadoop-tools/hadoop-aws/src/site/markdown/tools/hadoop-aws/index.md index 82c35885301..18c0cebdcba 100644 --- a/hadoop-tools/hadoop-aws/src/site/markdown/tools/hadoop-aws/index.md +++ b/hadoop-tools/hadoop-aws/src/site/markdown/tools/hadoop-aws/index.md @@ -41,6 +41,7 @@ The specifics of using these filesystems are documented in this section. See also: + * [Testing](testing.html) * [Troubleshooting S3a](troubleshooting_s3a.html) @@ -99,6 +100,7 @@ access to the data. Anyone with the credentials can not only read your datasets —they can delete them. Do not inadvertently share these credentials through means such as + 1. Checking in to SCM any configuration files containing the secrets. 1. Logging them to a console, as they invariably end up being seen. 1. Defining filesystem URIs with the credentials in the URL, such as diff --git a/hadoop-tools/hadoop-aws/src/site/markdown/tools/hadoop-aws/testing.md b/hadoop-tools/hadoop-aws/src/site/markdown/tools/hadoop-aws/testing.md index 79551a3290d..39ca8f42b0b 100644 --- a/hadoop-tools/hadoop-aws/src/site/markdown/tools/hadoop-aws/testing.md +++ b/hadoop-tools/hadoop-aws/src/site/markdown/tools/hadoop-aws/testing.md @@ -643,6 +643,7 @@ located. New tests are always welcome. Bear in mind that we need to keep costs and test time down, which is done by + * Not duplicating tests. * Being efficient in your use of Hadoop API calls. * Isolating large/slow tests into the "scale" test group. From 6eba79232f36b36e0196163adc8fe4219a6b6bf9 Mon Sep 17 00:00:00 2001 From: Chris Douglas Date: Mon, 3 Apr 2017 20:13:14 -0700 Subject: [PATCH 186/188] HADOOP-14271. Correct spelling of 'occurred' and variants. Contributed by Yeliang Cang --- .../org/apache/hadoop/util/Progressable.java | 2 +- .../hadoop/util/UTF8ByteArrayUtils.java | 4 +-- .../src/main/native/gtest/gtest-all.cc | 2 +- .../main/native/gtest/include/gtest/gtest.h | 2 +- .../hadoop/test/MultithreadedTestUtil.java | 2 +- .../apache/hadoop/hdfs/DFSInputStream.java | 2 +- .../hadoop/hdfs/DFSStripedInputStream.java | 2 +- .../hadoop/fs/http/server/FSOperations.java | 34 +++++++++---------- .../fsdataset/impl/BlockPoolSlice.java | 2 +- .../datanode/fsdataset/impl/FsVolumeImpl.java | 2 +- .../mapreduce/lib/jobcontrol/JobControl.java | 2 +- .../fs/azure/BlockBlobAppendStream.java | 2 +- .../hadoop/streaming/StreamKeyValUtil.java | 8 ++--- .../TestDistributedShell.java | 30 ++++++++-------- .../launcher/TestContainerLaunch.java | 12 +++---- .../scheduler/capacity/TestParentQueue.java | 18 +++++----- 16 files changed, 63 insertions(+), 63 deletions(-) diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/util/Progressable.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/util/Progressable.java index 495ca82b76e..201ee5c41c9 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/util/Progressable.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/util/Progressable.java @@ -28,7 +28,7 @@ import org.apache.hadoop.classification.InterfaceStability; * to explicitly report progress to the Hadoop framework. This is especially * important for operations which take significant amount of time since, * in-lieu of the reported progress, the framework has to assume that an error - * has occured and time-out the operation.

    + * has occurred and time-out the operation.

    */ @InterfaceAudience.Public @InterfaceStability.Stable diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/util/UTF8ByteArrayUtils.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/util/UTF8ByteArrayUtils.java index 2a804c618e1..069494f0f38 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/util/UTF8ByteArrayUtils.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/util/UTF8ByteArrayUtils.java @@ -30,7 +30,7 @@ public class UTF8ByteArrayUtils { * @param start starting offset * @param end ending position * @param b the byte to find - * @return position that first byte occures otherwise -1 + * @return position that first byte occurs, otherwise -1 */ public static int findByte(byte [] utf, int start, int end, byte b) { for(int i=start; itrue if the delete operation was successful, * false otherwise. * - * @throws IOException thrown if an IO error occured. + * @throws IOException thrown if an IO error occurred. */ @Override public JSONObject execute(FileSystem fs) throws IOException { @@ -583,7 +583,7 @@ public class FSOperations { * * @return a Map object (JSON friendly) with the file checksum. * - * @throws IOException thrown if an IO error occured. + * @throws IOException thrown if an IO error occurred. */ @Override public Map execute(FileSystem fs) throws IOException { @@ -640,7 +640,7 @@ public class FSOperations { * * @return a JSON object with the user home directory. * - * @throws IOException thrown if an IO error occured. + * @throws IOException thrown if an IO error occurred. */ @Override @SuppressWarnings("unchecked") @@ -765,7 +765,7 @@ public class FSOperations { * @return true if the mkdirs operation was successful, * false otherwise. * - * @throws IOException thrown if an IO error occured. + * @throws IOException thrown if an IO error occurred. */ @Override public JSONObject execute(FileSystem fs) throws IOException { @@ -799,7 +799,7 @@ public class FSOperations { * * @return The inputstream of the file. * - * @throws IOException thrown if an IO error occured. + * @throws IOException thrown if an IO error occurred. */ @Override public InputStream execute(FileSystem fs) throws IOException { @@ -837,7 +837,7 @@ public class FSOperations { * @return true if the rename operation was successful, * false otherwise. * - * @throws IOException thrown if an IO error occured. + * @throws IOException thrown if an IO error occurred. */ @Override public JSONObject execute(FileSystem fs) throws IOException { @@ -876,7 +876,7 @@ public class FSOperations { * * @return void. * - * @throws IOException thrown if an IO error occured. + * @throws IOException thrown if an IO error occurred. */ @Override public Void execute(FileSystem fs) throws IOException { @@ -913,7 +913,7 @@ public class FSOperations { * * @return void. * - * @throws IOException thrown if an IO error occured. + * @throws IOException thrown if an IO error occurred. */ @Override public Void execute(FileSystem fs) throws IOException { @@ -1186,7 +1186,7 @@ public class FSOperations { * @return true if the replication value was set, * false otherwise. * - * @throws IOException thrown if an IO error occured. + * @throws IOException thrown if an IO error occurred. */ @Override @SuppressWarnings("unchecked") @@ -1228,7 +1228,7 @@ public class FSOperations { * * @return void. * - * @throws IOException thrown if an IO error occured. + * @throws IOException thrown if an IO error occurred. */ @Override public Void execute(FileSystem fs) throws IOException { @@ -1314,7 +1314,7 @@ public class FSOperations { * * @return Map a map object (JSON friendly) with the xattr names. * - * @throws IOException thrown if an IO error occured. + * @throws IOException thrown if an IO error occurred. */ @Override public Map execute(FileSystem fs) throws IOException { @@ -1353,7 +1353,7 @@ public class FSOperations { * * @return Map a map object (JSON friendly) with the xattrs. * - * @throws IOException thrown if an IO error occured. + * @throws IOException thrown if an IO error occurred. */ @Override public Map execute(FileSystem fs) throws IOException { diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/fsdataset/impl/BlockPoolSlice.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/fsdataset/impl/BlockPoolSlice.java index c8df300ff6b..c17ef366271 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/fsdataset/impl/BlockPoolSlice.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/fsdataset/impl/BlockPoolSlice.java @@ -822,7 +822,7 @@ class BlockPoolSlice { } catch (Exception e) { // Any exception we need to revert back to read from disk // Log the error and return false - LOG.info("Exception occured while reading the replicas cache file: " + LOG.info("Exception occurred while reading the replicas cache file: " + replicaFile.getPath(), e ); return false; } diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/fsdataset/impl/FsVolumeImpl.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/fsdataset/impl/FsVolumeImpl.java index f6e6a59aa43..b948fb788a4 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/fsdataset/impl/FsVolumeImpl.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/fsdataset/impl/FsVolumeImpl.java @@ -1323,7 +1323,7 @@ public class FsVolumeImpl implements FsVolumeSpi { fileNames = fileIoProvider.listDirectory( this, dir, BlockDirFilter.INSTANCE); } catch (IOException ioe) { - LOG.warn("Exception occured while compiling report: ", ioe); + LOG.warn("Exception occurred while compiling report: ", ioe); // Volume error check moved to FileIoProvider. // Ignore this directory and proceed. return report; diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/lib/jobcontrol/JobControl.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/lib/jobcontrol/JobControl.java index b0b7a3c119f..e5399b5f744 100644 --- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/lib/jobcontrol/JobControl.java +++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/lib/jobcontrol/JobControl.java @@ -276,7 +276,7 @@ public class JobControl implements Runnable { } synchronized private void failAllJobs(Throwable t) { - String message = "Unexpected System Error Occured: "+ + String message = "Unexpected System Error Occurred: "+ StringUtils.stringifyException(t); Iterator it = jobsInProgress.iterator(); while(it.hasNext()) { diff --git a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/BlockBlobAppendStream.java b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/BlockBlobAppendStream.java index a7e286c917a..afb9379c3ca 100644 --- a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/BlockBlobAppendStream.java +++ b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/BlockBlobAppendStream.java @@ -346,7 +346,7 @@ public class BlockBlobAppendStream extends OutputStream { try { if (!ioThreadPool.awaitTermination(10, TimeUnit.MINUTES)) { - LOG.error("Time out occured while waiting for IO request to finish in append" + LOG.error("Time out occurred while waiting for IO request to finish in append" + " for blob : {}", key); NativeAzureFileSystemHelper.logAllLiveStackTraces(); throw new IOException("Timed out waiting for IO requests to finish"); diff --git a/hadoop-tools/hadoop-streaming/src/main/java/org/apache/hadoop/streaming/StreamKeyValUtil.java b/hadoop-tools/hadoop-streaming/src/main/java/org/apache/hadoop/streaming/StreamKeyValUtil.java index 75e05dc0708..fba45b11f24 100644 --- a/hadoop-tools/hadoop-streaming/src/main/java/org/apache/hadoop/streaming/StreamKeyValUtil.java +++ b/hadoop-tools/hadoop-streaming/src/main/java/org/apache/hadoop/streaming/StreamKeyValUtil.java @@ -26,11 +26,11 @@ import org.apache.hadoop.util.LineReader; public class StreamKeyValUtil { /** - * Find the first occured tab in a UTF-8 encoded string + * Find the first occurred tab in a UTF-8 encoded string * @param utf a byte array containing a UTF-8 encoded string * @param start starting offset * @param length no. of bytes - * @return position that first tab occures otherwise -1 + * @return position that first tab occurres otherwise -1 */ public static int findTab(byte [] utf, int start, int length) { for(int i=start; i<(start+length); i++) { @@ -41,9 +41,9 @@ public class StreamKeyValUtil { return -1; } /** - * Find the first occured tab in a UTF-8 encoded string + * Find the first occurred tab in a UTF-8 encoded string * @param utf a byte array containing a UTF-8 encoded string - * @return position that first tab occures otherwise -1 + * @return position that first tab occurres otherwise -1 */ public static int findTab(byte [] utf) { return org.apache.hadoop.util.UTF8ByteArrayUtils.findNthByte(utf, 0, diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-distributedshell/src/test/java/org/apache/hadoop/yarn/applications/distributedshell/TestDistributedShell.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-distributedshell/src/test/java/org/apache/hadoop/yarn/applications/distributedshell/TestDistributedShell.java index 300ea67497c..ef21c8786c5 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-distributedshell/src/test/java/org/apache/hadoop/yarn/applications/distributedshell/TestDistributedShell.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-distributedshell/src/test/java/org/apache/hadoop/yarn/applications/distributedshell/TestDistributedShell.java @@ -544,17 +544,17 @@ public class TestDistributedShell { Assert.assertEquals( "Container created event needs to be published atleast once", 1, - getNumOfStringOccurences(containerEntityFile, + getNumOfStringOccurrences(containerEntityFile, ContainerMetricsConstants.CREATED_EVENT_TYPE)); // to avoid race condition of testcase, atleast check 4 times with sleep // of 500ms - long numOfContainerFinishedOccurences = 0; + long numOfContainerFinishedOccurrences = 0; for (int i = 0; i < 4; i++) { - numOfContainerFinishedOccurences = - getNumOfStringOccurences(containerEntityFile, + numOfContainerFinishedOccurrences = + getNumOfStringOccurrences(containerEntityFile, ContainerMetricsConstants.FINISHED_EVENT_TYPE); - if (numOfContainerFinishedOccurences > 0) { + if (numOfContainerFinishedOccurrences > 0) { break; } else { Thread.sleep(500L); @@ -563,7 +563,7 @@ public class TestDistributedShell { Assert.assertEquals( "Container finished event needs to be published atleast once", 1, - numOfContainerFinishedOccurences); + numOfContainerFinishedOccurrences); // Verify RM posting Application life cycle Events are getting published String appMetricsTimestampFileName = @@ -576,17 +576,17 @@ public class TestDistributedShell { Assert.assertEquals( "Application created event should be published atleast once", 1, - getNumOfStringOccurences(appEntityFile, + getNumOfStringOccurrences(appEntityFile, ApplicationMetricsConstants.CREATED_EVENT_TYPE)); // to avoid race condition of testcase, atleast check 4 times with sleep // of 500ms - long numOfStringOccurences = 0; + long numOfStringOccurrences = 0; for (int i = 0; i < 4; i++) { - numOfStringOccurences = - getNumOfStringOccurences(appEntityFile, + numOfStringOccurrences = + getNumOfStringOccurrences(appEntityFile, ApplicationMetricsConstants.FINISHED_EVENT_TYPE); - if (numOfStringOccurences > 0) { + if (numOfStringOccurrences > 0) { break; } else { Thread.sleep(500L); @@ -595,7 +595,7 @@ public class TestDistributedShell { Assert.assertEquals( "Application finished event should be published atleast once", 1, - numOfStringOccurences); + numOfStringOccurrences); // Verify RM posting AppAttempt life cycle Events are getting published String appAttemptMetricsTimestampFileName = @@ -609,13 +609,13 @@ public class TestDistributedShell { Assert.assertEquals( "AppAttempt register event should be published atleast once", 1, - getNumOfStringOccurences(appAttemptEntityFile, + getNumOfStringOccurrences(appAttemptEntityFile, AppAttemptMetricsConstants.REGISTERED_EVENT_TYPE)); Assert.assertEquals( "AppAttempt finished event should be published atleast once", 1, - getNumOfStringOccurences(appAttemptEntityFile, + getNumOfStringOccurrences(appAttemptEntityFile, AppAttemptMetricsConstants.FINISHED_EVENT_TYPE)); } finally { FileUtils.deleteDirectory(tmpRootFolder.getParentFile()); @@ -636,7 +636,7 @@ public class TestDistributedShell { return entityFile; } - private long getNumOfStringOccurences(File entityFile, String searchString) + private long getNumOfStringOccurrences(File entityFile, String searchString) throws IOException { BufferedReader reader = null; String strLine; diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/launcher/TestContainerLaunch.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/launcher/TestContainerLaunch.java index 23b99d9a7e5..8dcf4be41f8 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/launcher/TestContainerLaunch.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/launcher/TestContainerLaunch.java @@ -641,8 +641,8 @@ public class TestContainerLaunch extends BaseContainerManagerTest { ContainerLaunch launch = new ContainerLaunch(context, conf, dispatcher, exec, app, container, dirsHandler, containerManager); launch.call(); - Assert.assertTrue("ContainerExitEvent should have occured", - eventHandler.isContainerExitEventOccured()); + Assert.assertTrue("ContainerExitEvent should have occurred", + eventHandler.isContainerExitEventOccurred()); } private static class ContainerExitHandler implements EventHandler { @@ -652,15 +652,15 @@ public class TestContainerLaunch extends BaseContainerManagerTest { this.testForMultiFile = testForMultiFile; } - boolean containerExitEventOccured = false; + boolean containerExitEventOccurred = false; - public boolean isContainerExitEventOccured() { - return containerExitEventOccured; + public boolean isContainerExitEventOccurred() { + return containerExitEventOccurred; } public void handle(Event event) { if (event instanceof ContainerExitEvent) { - containerExitEventOccured = true; + containerExitEventOccurred = true; ContainerExitEvent exitEvent = (ContainerExitEvent) event; Assert.assertEquals(ContainerEventType.CONTAINER_EXITED_WITH_FAILURE, exitEvent.getType()); diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/TestParentQueue.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/TestParentQueue.java index c4b7a0d4031..cdbbc519857 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/TestParentQueue.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/TestParentQueue.java @@ -343,43 +343,43 @@ public class TestParentQueue { csConf.setCapacity(Q_B, 70.5F); Map queues = new HashMap(); - boolean exceptionOccured = false; + boolean exceptionOccurred = false; try { CapacitySchedulerQueueManager.parseQueue(csContext, csConf, null, CapacitySchedulerConfiguration.ROOT, queues, queues, TestUtils.spyHook); } catch (IllegalArgumentException ie) { - exceptionOccured = true; + exceptionOccurred = true; } - if (!exceptionOccured) { + if (!exceptionOccurred) { Assert.fail("Capacity is more then 100% so should be failed."); } csConf.setCapacity(Q_A, 30); csConf.setCapacity(Q_B, 70); - exceptionOccured = false; + exceptionOccurred = false; queues.clear(); try { CapacitySchedulerQueueManager.parseQueue(csContext, csConf, null, CapacitySchedulerConfiguration.ROOT, queues, queues, TestUtils.spyHook); } catch (IllegalArgumentException ie) { - exceptionOccured = true; + exceptionOccurred = true; } - if (exceptionOccured) { + if (exceptionOccurred) { Assert.fail("Capacity is 100% so should not be failed."); } csConf.setCapacity(Q_A, 30); csConf.setCapacity(Q_B, 70.005F); - exceptionOccured = false; + exceptionOccurred = false; queues.clear(); try { CapacitySchedulerQueueManager.parseQueue(csContext, csConf, null, CapacitySchedulerConfiguration.ROOT, queues, queues, TestUtils.spyHook); } catch (IllegalArgumentException ie) { - exceptionOccured = true; + exceptionOccurred = true; } - if (exceptionOccured) { + if (exceptionOccurred) { Assert .fail("Capacity is under PRECISION which is .05% so should not be failed."); } From 27a44b60c1a55cabbf1873bca62db4e321c901ca Mon Sep 17 00:00:00 2001 From: Mingliang Liu Date: Tue, 4 Apr 2017 11:03:59 -0700 Subject: [PATCH 187/188] HADOOP-14272. Azure: WasbRemoteCallHelper should use String equals for comparison. Contributed by Santhosh G Nayak --- .../java/org/apache/hadoop/fs/azure/WasbRemoteCallHelper.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/WasbRemoteCallHelper.java b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/WasbRemoteCallHelper.java index 09ea0847ee4..b43e5aec2a0 100644 --- a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/WasbRemoteCallHelper.java +++ b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/WasbRemoteCallHelper.java @@ -88,7 +88,8 @@ class WasbRemoteCallHelper { } Header contentTypeHeader = response.getFirstHeader("Content-Type"); - if (contentTypeHeader == null || contentTypeHeader.getValue() != APPLICATION_JSON) { + if (contentTypeHeader == null + || !APPLICATION_JSON.equals(contentTypeHeader.getValue())) { throw new WasbRemoteCallException(getRequest.getURI().toString() + ":" + "Content-Type mismatch: expected: " + APPLICATION_JSON + ", got " + ((contentTypeHeader!=null) ? contentTypeHeader.getValue() : "NULL") From 56ab02eed9b61e1c80605104dfc4c87fc6abac96 Mon Sep 17 00:00:00 2001 From: Mingliang Liu Date: Tue, 4 Apr 2017 11:10:28 -0700 Subject: [PATCH 188/188] HADOOP-14273. Azure: NativeAzureFileSystem should respect config for kerberosSupportEnabled flag. Contributed by Santhosh G Nayak --- .../java/org/apache/hadoop/fs/azure/NativeAzureFileSystem.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/NativeAzureFileSystem.java b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/NativeAzureFileSystem.java index 1e6f00eb337..5469944f9b3 100644 --- a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/NativeAzureFileSystem.java +++ b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azure/NativeAzureFileSystem.java @@ -1287,6 +1287,8 @@ public class NativeAzureFileSystem extends FileSystem { this.azureAuthorization = useSecureMode && conf.getBoolean(KEY_AZURE_AUTHORIZATION, DEFAULT_AZURE_AUTHORIZATION); + this.kerberosSupportEnabled = + conf.getBoolean(Constants.AZURE_KERBEROS_SUPPORT_PROPERTY_NAME, false); if (this.azureAuthorization) {
    Node Health Report {{model.node.healthReport}}
    Node Manager Start Time{{model.node.nmStartupTime}}
    Node Manager Start Time{{model.node.nmStartupTime}}
    Node Manager Version {{model.node.nodeManagerBuildVersion}}