HADOOP-10919. Copy command should preserve raw.* namespace extended attributes. (clamb)

git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/branches/fs-encryption@1616840 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Charles Lamb 2014-08-08 17:58:01 +00:00
parent 1fa2d6c4ba
commit e6bdb33784
5 changed files with 254 additions and 18 deletions

View File

@ -46,6 +46,9 @@ fs-encryption (Unreleased)
HADOOP-10853. Refactor get instance of CryptoCodec and support create via HADOOP-10853. Refactor get instance of CryptoCodec and support create via
algorithm/mode/padding. (Yi Liu) algorithm/mode/padding. (Yi Liu)
HADOOP-10919. Copy command should preserve raw.* namespace
extended attributes. (clamb)
OPTIMIZATIONS OPTIMIZATIONS
BUG FIXES BUG FIXES

View File

@ -57,6 +57,17 @@ abstract class CommandWithDestination extends FsCommand {
private boolean verifyChecksum = true; private boolean verifyChecksum = true;
private boolean writeChecksum = true; private boolean writeChecksum = true;
/**
* The name of the raw xattr namespace. It would be nice to use
* XAttr.RAW.name() but we can't reference the hadoop-hdfs project.
*/
private final String RAW = "raw.";
/**
* The name of the reserved raw directory.
*/
private final String RESERVED_RAW = "/.reserved/raw";
/** /**
* *
* This method is used to enable the force(-f) option while copying the files. * This method is used to enable the force(-f) option while copying the files.
@ -231,7 +242,7 @@ abstract class CommandWithDestination extends FsCommand {
/** /**
* Called with a source and target destination pair * Called with a source and target destination pair
* @param src for the operation * @param src for the operation
* @param target for the operation * @param dst for the operation
* @throws IOException if anything goes wrong * @throws IOException if anything goes wrong
*/ */
protected void processPath(PathData src, PathData dst) throws IOException { protected void processPath(PathData src, PathData dst) throws IOException {
@ -253,6 +264,8 @@ abstract class CommandWithDestination extends FsCommand {
// modify dst as we descend to append the basename of the // modify dst as we descend to append the basename of the
// current directory being processed // current directory being processed
dst = getTargetPath(src); dst = getTargetPath(src);
final boolean preserveRawXattrs =
checkPathsForReservedRaw(src.path, dst.path);
if (dst.exists) { if (dst.exists) {
if (!dst.stat.isDirectory()) { if (!dst.stat.isDirectory()) {
throw new PathIsNotDirectoryException(dst.toString()); throw new PathIsNotDirectoryException(dst.toString());
@ -268,7 +281,7 @@ abstract class CommandWithDestination extends FsCommand {
} }
super.recursePath(src); super.recursePath(src);
if (dst.stat.isDirectory()) { if (dst.stat.isDirectory()) {
preserveAttributes(src, dst); preserveAttributes(src, dst, preserveRawXattrs);
} }
} finally { } finally {
dst = savedDst; dst = savedDst;
@ -295,18 +308,60 @@ abstract class CommandWithDestination extends FsCommand {
* @param target where to copy the item * @param target where to copy the item
* @throws IOException if copy fails * @throws IOException if copy fails
*/ */
protected void copyFileToTarget(PathData src, PathData target) throws IOException { protected void copyFileToTarget(PathData src, PathData target)
throws IOException {
final boolean preserveRawXattrs =
checkPathsForReservedRaw(src.path, target.path);
src.fs.setVerifyChecksum(verifyChecksum); src.fs.setVerifyChecksum(verifyChecksum);
InputStream in = null; InputStream in = null;
try { try {
in = src.fs.open(src.path); in = src.fs.open(src.path);
copyStreamToTarget(in, target); copyStreamToTarget(in, target);
preserveAttributes(src, target); preserveAttributes(src, target, preserveRawXattrs);
} finally { } finally {
IOUtils.closeStream(in); IOUtils.closeStream(in);
} }
} }
/**
* Check the source and target paths to ensure that they are either both in
* /.reserved/raw or neither in /.reserved/raw. If neither src nor target are
* in /.reserved/raw, then return false, indicating not to preserve raw.*
* xattrs. If both src/target are in /.reserved/raw, then return true,
* indicating raw.* xattrs should be preserved. If only one of src/target is
* in /.reserved/raw then throw an exception.
*
* @param src The source path to check. This should be a fully-qualified
* path, not relative.
* @param target The target path to check. This should be a fully-qualified
* path, not relative.
* @return true if raw.* xattrs should be preserved.
* @throws PathOperationException is only one of src/target are in
* /.reserved/raw.
*/
private boolean checkPathsForReservedRaw(Path src, Path target)
throws PathOperationException {
final boolean srcIsRR = Path.getPathWithoutSchemeAndAuthority(src).
toString().startsWith(RESERVED_RAW);
final boolean dstIsRR = Path.getPathWithoutSchemeAndAuthority(target).
toString().startsWith(RESERVED_RAW);
boolean preserveRawXattrs = false;
if (srcIsRR && !dstIsRR) {
final String s = "' copy from '" + RESERVED_RAW + "' to non '" +
RESERVED_RAW + "'. Either both source and target must be in '" +
RESERVED_RAW + "' or neither.";
throw new PathOperationException("'" + src.toString() + s);
} else if (!srcIsRR && dstIsRR) {
final String s = "' copy from non '" + RESERVED_RAW +"' to '" +
RESERVED_RAW + "'. Either both source and target must be in '" +
RESERVED_RAW + "' or neither.";
throw new PathOperationException("'" + dst.toString() + s);
} else if (srcIsRR && dstIsRR) {
preserveRawXattrs = true;
}
return preserveRawXattrs;
}
/** /**
* Copies the stream contents to a temporary file. If the copy is * Copies the stream contents to a temporary file. If the copy is
* successful, the temporary file will be renamed to the real path, * successful, the temporary file will be renamed to the real path,
@ -337,9 +392,11 @@ abstract class CommandWithDestination extends FsCommand {
* attribute to preserve. * attribute to preserve.
* @param src source to preserve * @param src source to preserve
* @param target where to preserve attributes * @param target where to preserve attributes
* @param preserveRawXAttrs true if raw.* xattrs should be preserved
* @throws IOException if fails to preserve attributes * @throws IOException if fails to preserve attributes
*/ */
protected void preserveAttributes(PathData src, PathData target) protected void preserveAttributes(PathData src, PathData target,
boolean preserveRawXAttrs)
throws IOException { throws IOException {
if (shouldPreserve(FileAttribute.TIMESTAMPS)) { if (shouldPreserve(FileAttribute.TIMESTAMPS)) {
target.fs.setTimes( target.fs.setTimes(
@ -369,13 +426,17 @@ abstract class CommandWithDestination extends FsCommand {
target.fs.setAcl(target.path, srcFullEntries); target.fs.setAcl(target.path, srcFullEntries);
} }
} }
if (shouldPreserve(FileAttribute.XATTR)) { final boolean preserveXAttrs = shouldPreserve(FileAttribute.XATTR);
if (preserveXAttrs || preserveRawXAttrs) {
Map<String, byte[]> srcXAttrs = src.fs.getXAttrs(src.path); Map<String, byte[]> srcXAttrs = src.fs.getXAttrs(src.path);
if (srcXAttrs != null) { if (srcXAttrs != null) {
Iterator<Entry<String, byte[]>> iter = srcXAttrs.entrySet().iterator(); Iterator<Entry<String, byte[]>> iter = srcXAttrs.entrySet().iterator();
while (iter.hasNext()) { while (iter.hasNext()) {
Entry<String, byte[]> entry = iter.next(); Entry<String, byte[]> entry = iter.next();
target.fs.setXAttr(target.path, entry.getKey(), entry.getValue()); final String xattrName = entry.getKey();
if (xattrName.startsWith(RAW) || preserveXAttrs) {
target.fs.setXAttr(target.path, entry.getKey(), entry.getValue());
}
} }
} }
} }

View File

@ -143,7 +143,11 @@ class CopyCommands {
"timestamps, ownership, permission. If -pa is specified, " + "timestamps, ownership, permission. If -pa is specified, " +
"then preserves permission also because ACL is a super-set of " + "then preserves permission also because ACL is a super-set of " +
"permission. Passing -f overwrites the destination if it " + "permission. Passing -f overwrites the destination if it " +
"already exists.\n"; "already exists. raw namespace extended attributes are preserved " +
"if (1) they are supported (HDFS only) and, (2) all of the source and " +
"target pathnames are in the /.reserved/raw hierarchy. raw namespace " +
"xattr preservation is determined solely by the presence (or absence) " +
"of the /.reserved/raw prefix and not by the -p option.\n";
@Override @Override
protected void processOptions(LinkedList<String> args) throws IOException { protected void processOptions(LinkedList<String> args) throws IOException {

View File

@ -164,15 +164,22 @@ cp
Copy files from source to destination. This command allows multiple sources Copy files from source to destination. This command allows multiple sources
as well in which case the destination must be a directory. as well in which case the destination must be a directory.
'raw.*' namespace extended attributes are preserved if (1) the source and
destination filesystems support them (HDFS only), and (2) all source and
destination pathnames are in the /.reserved/raw hierarchy. Determination of
whether raw.* namespace xattrs are preserved is independent of the
-p (preserve) flag.
Options: Options:
* The -f option will overwrite the destination if it already exists. * The -f option will overwrite the destination if it already exists.
* The -p option will preserve file attributes [topx] (timestamps, * The -p option will preserve file attributes [topx] (timestamps,
ownership, permission, ACL, XAttr). If -p is specified with no <arg>, ownership, permission, ACL, XAttr). If -p is specified with no <arg>,
then preserves timestamps, ownership, permission. If -pa is specified, then preserves timestamps, ownership, permission. If -pa is specified,
then preserves permission also because ACL is a super-set of then preserves permission also because ACL is a super-set of
permission. permission. Determination of whether raw namespace extended attributes
are preserved is independent of the -p flag.
Example: Example:

View File

@ -77,6 +77,13 @@ public class TestDFSShell {
static final String TEST_ROOT_DIR = PathUtils.getTestDirName(TestDFSShell.class); static final String TEST_ROOT_DIR = PathUtils.getTestDirName(TestDFSShell.class);
private static final String RAW_A1 = "raw.a1";
private static final String TRUSTED_A1 = "trusted.a1";
private static final String USER_A1 = "user.a1";
private static final byte[] RAW_A1_VALUE = new byte[]{0x32, 0x32, 0x32};
private static final byte[] TRUSTED_A1_VALUE = new byte[]{0x31, 0x31, 0x31};
private static final byte[] USER_A1_VALUE = new byte[]{0x31, 0x32, 0x33};
static Path writeFile(FileSystem fs, Path f) throws IOException { static Path writeFile(FileSystem fs, Path f) throws IOException {
DataOutputStream out = fs.create(f); DataOutputStream out = fs.create(f);
out.writeBytes("dhruba: " + f); out.writeBytes("dhruba: " + f);
@ -1664,8 +1671,8 @@ public class TestDFSShell {
final String group = status.getGroup(); final String group = status.getGroup();
final FsPermission perm = status.getPermission(); final FsPermission perm = status.getPermission();
fs.setXAttr(src, "user.a1", new byte[]{0x31, 0x32, 0x33}); fs.setXAttr(src, USER_A1, USER_A1_VALUE);
fs.setXAttr(src, "trusted.a1", new byte[]{0x31, 0x31, 0x31}); fs.setXAttr(src, TRUSTED_A1, TRUSTED_A1_VALUE);
shell = new FsShell(conf); shell = new FsShell(conf);
@ -1722,8 +1729,8 @@ public class TestDFSShell {
assertTrue(perm.equals(targetPerm)); assertTrue(perm.equals(targetPerm));
xattrs = fs.getXAttrs(target3); xattrs = fs.getXAttrs(target3);
assertEquals(xattrs.size(), 2); assertEquals(xattrs.size(), 2);
assertArrayEquals(new byte[]{0x31, 0x32, 0x33}, xattrs.get("user.a1")); assertArrayEquals(USER_A1_VALUE, xattrs.get(USER_A1));
assertArrayEquals(new byte[]{0x31, 0x31, 0x31}, xattrs.get("trusted.a1")); assertArrayEquals(TRUSTED_A1_VALUE, xattrs.get(TRUSTED_A1));
acls = fs.getAclStatus(target3).getEntries(); acls = fs.getAclStatus(target3).getEntries();
assertTrue(acls.isEmpty()); assertTrue(acls.isEmpty());
assertFalse(targetPerm.getAclBit()); assertFalse(targetPerm.getAclBit());
@ -1780,6 +1787,160 @@ public class TestDFSShell {
} }
} }
@Test (timeout = 120000)
public void testCopyCommandsWithRawXAttrs() throws Exception {
final Configuration conf = new Configuration();
conf.setBoolean(DFSConfigKeys.DFS_NAMENODE_XATTRS_ENABLED_KEY, true);
final MiniDFSCluster cluster = new MiniDFSCluster.Builder(conf).
numDataNodes(1).format(true).build();
FsShell shell = null;
FileSystem fs = null;
final String testdir = "/tmp/TestDFSShell-testCopyCommandsWithRawXAttrs-"
+ counter.getAndIncrement();
final Path hdfsTestDir = new Path(testdir);
final Path rawHdfsTestDir = new Path("/.reserved/raw" + testdir);
try {
fs = cluster.getFileSystem();
fs.mkdirs(hdfsTestDir);
final Path src = new Path(hdfsTestDir, "srcfile");
final String rawSrcBase = "/.reserved/raw" + testdir;
final Path rawSrc = new Path(rawSrcBase, "srcfile");
fs.create(src).close();
final Path srcDir = new Path(hdfsTestDir, "srcdir");
final Path rawSrcDir = new Path("/.reserved/raw" + testdir, "srcdir");
fs.mkdirs(srcDir);
final Path srcDirFile = new Path(srcDir, "srcfile");
final Path rawSrcDirFile =
new Path("/.reserved/raw" + srcDirFile);
fs.create(srcDirFile).close();
final Path[] paths = { rawSrc, rawSrcDir, rawSrcDirFile };
final String[] xattrNames = { USER_A1, RAW_A1 };
final byte[][] xattrVals = { USER_A1_VALUE, RAW_A1_VALUE };
for (int i = 0; i < paths.length; i++) {
for (int j = 0; j < xattrNames.length; j++) {
fs.setXAttr(paths[i], xattrNames[j], xattrVals[j]);
}
}
shell = new FsShell(conf);
/* Check that a file as the source path works ok. */
doTestCopyCommandsWithRawXAttrs(shell, fs, src, hdfsTestDir, false);
doTestCopyCommandsWithRawXAttrs(shell, fs, rawSrc, hdfsTestDir, false);
doTestCopyCommandsWithRawXAttrs(shell, fs, src, rawHdfsTestDir, false);
doTestCopyCommandsWithRawXAttrs(shell, fs, rawSrc, rawHdfsTestDir, true);
/* Use a relative /.reserved/raw path. */
final Path savedWd = fs.getWorkingDirectory();
try {
fs.setWorkingDirectory(new Path(rawSrcBase));
final Path relRawSrc = new Path("../srcfile");
final Path relRawHdfsTestDir = new Path("..");
doTestCopyCommandsWithRawXAttrs(shell, fs, relRawSrc, relRawHdfsTestDir,
true);
} finally {
fs.setWorkingDirectory(savedWd);
}
/* Check that a directory as the source path works ok. */
doTestCopyCommandsWithRawXAttrs(shell, fs, srcDir, hdfsTestDir, false);
doTestCopyCommandsWithRawXAttrs(shell, fs, rawSrcDir, hdfsTestDir, false);
doTestCopyCommandsWithRawXAttrs(shell, fs, srcDir, rawHdfsTestDir, false);
doTestCopyCommandsWithRawXAttrs(shell, fs, rawSrcDir, rawHdfsTestDir,
true);
/* Use relative in an absolute path. */
final String relRawSrcDir = "./.reserved/../.reserved/raw/../raw" +
testdir + "/srcdir";
final String relRawDstDir = "./.reserved/../.reserved/raw/../raw" +
testdir;
doTestCopyCommandsWithRawXAttrs(shell, fs, new Path(relRawSrcDir),
new Path(relRawDstDir), true);
} finally {
if (null != shell) {
shell.close();
}
if (null != fs) {
fs.delete(hdfsTestDir, true);
fs.close();
}
cluster.shutdown();
}
}
private void doTestCopyCommandsWithRawXAttrs(FsShell shell, FileSystem fs,
Path src, Path hdfsTestDir, boolean expectRaw) throws Exception {
Path target;
boolean srcIsRaw;
if (src.isAbsolute()) {
srcIsRaw = src.toString().contains("/.reserved/raw");
} else {
srcIsRaw = new Path(fs.getWorkingDirectory(), src).
toString().contains("/.reserved/raw");
}
final boolean destIsRaw = hdfsTestDir.toString().contains("/.reserved/raw");
final boolean srcDestMismatch = srcIsRaw ^ destIsRaw;
// -p (possibly preserve raw if src & dst are both /.r/r */
if (srcDestMismatch) {
doCopyAndTest(shell, hdfsTestDir, src, "-p", ERROR);
} else {
target = doCopyAndTest(shell, hdfsTestDir, src, "-p", SUCCESS);
checkXAttrs(fs, target, expectRaw, false);
}
// -px (possibly preserve raw, always preserve non-raw xattrs. */
if (srcDestMismatch) {
doCopyAndTest(shell, hdfsTestDir, src, "-px", ERROR);
} else {
target = doCopyAndTest(shell, hdfsTestDir, src, "-px", SUCCESS);
checkXAttrs(fs, target, expectRaw, true);
}
// no args (possibly preserve raw, never preserve non-raw xattrs. */
if (srcDestMismatch) {
doCopyAndTest(shell, hdfsTestDir, src, null, ERROR);
} else {
target = doCopyAndTest(shell, hdfsTestDir, src, null, SUCCESS);
checkXAttrs(fs, target, expectRaw, false);
}
}
private Path doCopyAndTest(FsShell shell, Path dest, Path src,
String cpArgs, int expectedExitCode) throws Exception {
final Path target = new Path(dest, "targetfile" +
counter.getAndIncrement());
final String[] argv = cpArgs == null ?
new String[] { "-cp", src.toUri().toString(),
target.toUri().toString() } :
new String[] { "-cp", cpArgs, src.toUri().toString(),
target.toUri().toString() };
final int ret = ToolRunner.run(shell, argv);
assertEquals("cp -p is not working", expectedExitCode, ret);
return target;
}
private void checkXAttrs(FileSystem fs, Path target, boolean expectRaw,
boolean expectVanillaXAttrs) throws Exception {
final Map<String, byte[]> xattrs = fs.getXAttrs(target);
int expectedCount = 0;
if (expectRaw) {
assertArrayEquals("raw.a1 has incorrect value",
RAW_A1_VALUE, xattrs.get(RAW_A1));
expectedCount++;
}
if (expectVanillaXAttrs) {
assertArrayEquals("user.a1 has incorrect value",
USER_A1_VALUE, xattrs.get(USER_A1));
expectedCount++;
}
assertEquals("xattrs size mismatch", expectedCount, xattrs.size());
}
// verify cp -ptopxa option will preserve directory attributes. // verify cp -ptopxa option will preserve directory attributes.
@Test (timeout = 120000) @Test (timeout = 120000)
public void testCopyCommandsToDirectoryWithPreserveOption() public void testCopyCommandsToDirectoryWithPreserveOption()
@ -1825,8 +1986,8 @@ public class TestDFSShell {
final String group = status.getGroup(); final String group = status.getGroup();
final FsPermission perm = status.getPermission(); final FsPermission perm = status.getPermission();
fs.setXAttr(srcDir, "user.a1", new byte[]{0x31, 0x32, 0x33}); fs.setXAttr(srcDir, USER_A1, USER_A1_VALUE);
fs.setXAttr(srcDir, "trusted.a1", new byte[]{0x31, 0x31, 0x31}); fs.setXAttr(srcDir, TRUSTED_A1, TRUSTED_A1_VALUE);
shell = new FsShell(conf); shell = new FsShell(conf);
@ -1883,8 +2044,8 @@ public class TestDFSShell {
assertTrue(perm.equals(targetPerm)); assertTrue(perm.equals(targetPerm));
xattrs = fs.getXAttrs(targetDir3); xattrs = fs.getXAttrs(targetDir3);
assertEquals(xattrs.size(), 2); assertEquals(xattrs.size(), 2);
assertArrayEquals(new byte[]{0x31, 0x32, 0x33}, xattrs.get("user.a1")); assertArrayEquals(USER_A1_VALUE, xattrs.get(USER_A1));
assertArrayEquals(new byte[]{0x31, 0x31, 0x31}, xattrs.get("trusted.a1")); assertArrayEquals(TRUSTED_A1_VALUE, xattrs.get(TRUSTED_A1));
acls = fs.getAclStatus(targetDir3).getEntries(); acls = fs.getAclStatus(targetDir3).getEntries();
assertTrue(acls.isEmpty()); assertTrue(acls.isEmpty());
assertFalse(targetPerm.getAclBit()); assertFalse(targetPerm.getAclBit());