HADOOP-14839. DistCp log output should contain copied and deleted files and directories. Contributed by Yiqun Lin.
This commit is contained in:
parent
50506e90a8
commit
63720ef574
@ -45,6 +45,7 @@ public class DistCpConstants {
|
||||
public static final String CONF_LABEL_ATOMIC_COPY = "distcp.atomic.copy";
|
||||
public static final String CONF_LABEL_WORK_PATH = "distcp.work.path";
|
||||
public static final String CONF_LABEL_LOG_PATH = "distcp.log.path";
|
||||
public static final String CONF_LABEL_VERBOSE_LOG = "distcp.verbose.log";
|
||||
public static final String CONF_LABEL_IGNORE_FAILURES = "distcp.ignore.failures";
|
||||
public static final String CONF_LABEL_PRESERVE_STATUS = "distcp.preserve.status";
|
||||
public static final String CONF_LABEL_PRESERVE_RAWXATTRS =
|
||||
|
@ -118,6 +118,13 @@ public enum DistCpOptionSwitch {
|
||||
LOG_PATH(DistCpConstants.CONF_LABEL_LOG_PATH,
|
||||
new Option("log", true, "Folder on DFS where distcp execution logs are saved")),
|
||||
|
||||
/**
|
||||
* Log additional info (path, size) in the SKIP/COPY log.
|
||||
*/
|
||||
VERBOSE_LOG(DistCpConstants.CONF_LABEL_VERBOSE_LOG,
|
||||
new Option("v", false,
|
||||
"Log additional info (path, size) in the SKIP/COPY log")),
|
||||
|
||||
/**
|
||||
* Copy strategy is use. This could be dynamic or uniform size etc.
|
||||
* DistCp would use an appropriate input format based on this.
|
||||
|
@ -101,6 +101,9 @@ public final class DistCpOptions {
|
||||
// content at their s1, if src is not the same as tgt.
|
||||
private final boolean useRdiff;
|
||||
|
||||
/** Whether to log additional info (path, size) in the SKIP/COPY log. */
|
||||
private final boolean verboseLog;
|
||||
|
||||
// For both -diff and -rdiff, given the example command line switches, two
|
||||
// steps are taken:
|
||||
// 1. Sync Step. This step does renaming/deletion ops in the snapshot diff,
|
||||
@ -204,6 +207,7 @@ private DistCpOptions(Builder builder) {
|
||||
this.blocksPerChunk = builder.blocksPerChunk;
|
||||
|
||||
this.copyBufferSize = builder.copyBufferSize;
|
||||
this.verboseLog = builder.verboseLog;
|
||||
}
|
||||
|
||||
public Path getSourceFileListing() {
|
||||
@ -323,6 +327,10 @@ public int getCopyBufferSize() {
|
||||
return copyBufferSize;
|
||||
}
|
||||
|
||||
public boolean shouldVerboseLog() {
|
||||
return verboseLog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add options to configuration. These will be used in the Mapper/committer
|
||||
*
|
||||
@ -361,6 +369,8 @@ public void appendToConf(Configuration conf) {
|
||||
String.valueOf(blocksPerChunk));
|
||||
DistCpOptionSwitch.addToConf(conf, DistCpOptionSwitch.COPY_BUFFER_SIZE,
|
||||
String.valueOf(copyBufferSize));
|
||||
DistCpOptionSwitch.addToConf(conf, DistCpOptionSwitch.VERBOSE_LOG,
|
||||
String.valueOf(verboseLog));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -396,6 +406,7 @@ public String toString() {
|
||||
", filtersFile='" + filtersFile + '\'' +
|
||||
", blocksPerChunk=" + blocksPerChunk +
|
||||
", copyBufferSize=" + copyBufferSize +
|
||||
", verboseLog=" + verboseLog +
|
||||
'}';
|
||||
}
|
||||
|
||||
@ -420,6 +431,7 @@ public static class Builder {
|
||||
private boolean append = false;
|
||||
private boolean skipCRC = false;
|
||||
private boolean blocking = true;
|
||||
private boolean verboseLog = false;
|
||||
|
||||
private boolean useDiff = false;
|
||||
private boolean useRdiff = false;
|
||||
@ -552,6 +564,11 @@ private void validate() {
|
||||
throw new IllegalArgumentException(
|
||||
"-diff and -rdiff are mutually exclusive");
|
||||
}
|
||||
|
||||
if (verboseLog && logPath == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"-v is valid only with -log option");
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@ -685,6 +702,11 @@ public Builder withCopyBufferSize(int newCopyBufferSize) {
|
||||
: DistCpConstants.COPY_BUFFER_SIZE_DEFAULT;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withVerboseLog(boolean newVerboseLog) {
|
||||
this.verboseLog = newVerboseLog;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -111,7 +111,9 @@ public static DistCpOptions parse(String[] args)
|
||||
.withCRC(
|
||||
command.hasOption(DistCpOptionSwitch.SKIP_CRC.getSwitch()))
|
||||
.withBlocking(
|
||||
!command.hasOption(DistCpOptionSwitch.BLOCKING.getSwitch()));
|
||||
!command.hasOption(DistCpOptionSwitch.BLOCKING.getSwitch()))
|
||||
.withVerboseLog(
|
||||
command.hasOption(DistCpOptionSwitch.VERBOSE_LOG.getSwitch()));
|
||||
|
||||
if (command.hasOption(DistCpOptionSwitch.DIFF.getSwitch())) {
|
||||
String[] snapshots = getVals(command,
|
||||
|
@ -54,6 +54,7 @@ public class CopyMapper extends Mapper<Text, CopyListingFileStatus, Text, Text>
|
||||
*/
|
||||
public static enum Counter {
|
||||
COPY, // Number of files received by the mapper for copy.
|
||||
DIR_COPY, // Number of directories received by the mapper for copy.
|
||||
SKIP, // Number of files skipped.
|
||||
FAIL, // Number of files that failed to be copied.
|
||||
BYTESCOPIED, // Number of bytes actually copied by the copy-mapper, total.
|
||||
@ -82,6 +83,7 @@ static enum FileAction {
|
||||
private boolean skipCrc = false;
|
||||
private boolean overWrite = false;
|
||||
private boolean append = false;
|
||||
private boolean verboseLog = false;
|
||||
private EnumSet<FileAttribute> preserve = EnumSet.noneOf(FileAttribute.class);
|
||||
|
||||
private FileSystem targetFS = null;
|
||||
@ -105,6 +107,8 @@ public void setup(Context context) throws IOException, InterruptedException {
|
||||
skipCrc = conf.getBoolean(DistCpOptionSwitch.SKIP_CRC.getConfigLabel(), false);
|
||||
overWrite = conf.getBoolean(DistCpOptionSwitch.OVERWRITE.getConfigLabel(), false);
|
||||
append = conf.getBoolean(DistCpOptionSwitch.APPEND.getConfigLabel(), false);
|
||||
verboseLog = conf.getBoolean(
|
||||
DistCpOptionSwitch.VERBOSE_LOG.getConfigLabel(), false);
|
||||
preserve = DistCpUtils.unpackAttributes(conf.get(DistCpOptionSwitch.
|
||||
PRESERVE_STATUS.getConfigLabel()));
|
||||
|
||||
@ -196,6 +200,13 @@ public void map(Text relPath, CopyListingFileStatus sourceFileStatus,
|
||||
updateSkipCounters(context, sourceCurrStatus);
|
||||
context.write(null, new Text("SKIP: " + sourceCurrStatus.getPath()));
|
||||
|
||||
if (verboseLog) {
|
||||
context.write(null,
|
||||
new Text("FILE_SKIPPED: source=" + sourceFileStatus.getPath()
|
||||
+ ", size=" + sourceFileStatus.getLen() + " --> "
|
||||
+ "target=" + target + ", size=" + (targetStatus == null ?
|
||||
0 : targetStatus.getLen())));
|
||||
}
|
||||
} else {
|
||||
if (sourceCurrStatus.isSplit()) {
|
||||
tmpTarget = DistCpUtils.getSplitChunkPath(target, sourceCurrStatus);
|
||||
@ -203,8 +214,8 @@ public void map(Text relPath, CopyListingFileStatus sourceFileStatus,
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("copying " + sourceCurrStatus + " " + tmpTarget);
|
||||
}
|
||||
copyFileWithRetry(description, sourceCurrStatus, tmpTarget, context,
|
||||
action, fileAttributes);
|
||||
copyFileWithRetry(description, sourceCurrStatus, tmpTarget,
|
||||
targetStatus, context, action, fileAttributes);
|
||||
}
|
||||
DistCpUtils.preserve(target.getFileSystem(conf), tmpTarget,
|
||||
sourceCurrStatus, fileAttributes, preserveRawXattrs);
|
||||
@ -235,9 +246,10 @@ private String getFileType(FileStatus fileStatus) {
|
||||
}
|
||||
|
||||
private void copyFileWithRetry(String description,
|
||||
CopyListingFileStatus sourceFileStatus, Path target, Context context,
|
||||
FileAction action, EnumSet<DistCpOptions.FileAttribute> fileAttributes)
|
||||
throws IOException {
|
||||
CopyListingFileStatus sourceFileStatus, Path target,
|
||||
FileStatus targrtFileStatus, Context context, FileAction action,
|
||||
EnumSet<DistCpOptions.FileAttribute> fileAttributes)
|
||||
throws IOException, InterruptedException {
|
||||
long bytesCopied;
|
||||
try {
|
||||
bytesCopied = (Long) new RetriableFileCopyCommand(skipCrc, description,
|
||||
@ -251,6 +263,14 @@ private void copyFileWithRetry(String description,
|
||||
incrementCounter(context, Counter.BYTESCOPIED, bytesCopied);
|
||||
incrementCounter(context, Counter.COPY, 1);
|
||||
totalBytesCopied += bytesCopied;
|
||||
|
||||
if (verboseLog) {
|
||||
context.write(null,
|
||||
new Text("FILE_COPIED: source=" + sourceFileStatus.getPath() + ","
|
||||
+ " size=" + sourceFileStatus.getLen() + " --> "
|
||||
+ "target=" + target + ", size=" + (targrtFileStatus == null ?
|
||||
0 : targrtFileStatus.getLen())));
|
||||
}
|
||||
}
|
||||
|
||||
private void createTargetDirsWithRetry(String description,
|
||||
@ -260,7 +280,7 @@ private void createTargetDirsWithRetry(String description,
|
||||
} catch (Exception e) {
|
||||
throw new IOException("mkdir failed for " + target, e);
|
||||
}
|
||||
incrementCounter(context, Counter.COPY, 1);
|
||||
incrementCounter(context, Counter.DIR_COPY, 1);
|
||||
}
|
||||
|
||||
private static void updateSkipCounters(Context context,
|
||||
|
@ -220,6 +220,7 @@ 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. The option -pr is only valid if both source and target directory are not erasure coded. **Note:** If -p option's are not specified, then by default block size is preserved.
|
||||
`-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 <logdir>` | Write logs to \<logdir\> | 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.
|
||||
`-v` | Log additional info (path, size) in the SKIP/COPY log | This option can only be used with -log option.
|
||||
`-m <num_maps>` | Maximum number of simultaneous copies | Specify the number of maps to copy data. Note that more maps may not necessarily improve throughput.
|
||||
`-overwrite` | Overwrite destination | If a map fails and `-i` is not specified, all the files in the split, not only those that failed, will be recopied. As discussed in the Usage documentation, it also changes the semantics for generating destination paths, so users should use this carefully.
|
||||
`-update` | Overwrite if source and destination differ in size, blocksize, or checksum | As noted in the preceding, this is not a "sync" operation. The criteria examined are the source and destination file sizes, blocksizes, and checksums; if they differ, the source file replaces the destination file. As discussed in the Usage documentation, it also changes the semantics for generating destination paths, so users should use this carefully.
|
||||
|
@ -287,7 +287,7 @@ public void testToString() {
|
||||
"mapBandwidth=0.0, copyStrategy='uniformsize', preserveStatus=[], " +
|
||||
"atomicWorkPath=null, logPath=null, sourceFileListing=abc, " +
|
||||
"sourcePaths=null, targetPath=xyz, filtersFile='null'," +
|
||||
" blocksPerChunk=0, copyBufferSize=8192}";
|
||||
" blocksPerChunk=0, copyBufferSize=8192, verboseLog=false}";
|
||||
String optionString = option.toString();
|
||||
Assert.assertEquals(val, optionString);
|
||||
Assert.assertNotSame(DistCpOptionSwitch.ATOMIC_COMMIT.toString(),
|
||||
@ -514,4 +514,23 @@ public void testSetCopyBufferSize() {
|
||||
Assert.assertEquals(DistCpConstants.COPY_BUFFER_SIZE_DEFAULT,
|
||||
builder.build().getCopyBufferSize());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVerboseLog() {
|
||||
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().shouldVerboseLog());
|
||||
|
||||
try {
|
||||
builder.withVerboseLog(true).build();
|
||||
fail("-v should fail if -log option is not specified");
|
||||
} catch (IllegalArgumentException e) {
|
||||
assertExceptionContains("-v is valid only with -log option", e);
|
||||
}
|
||||
|
||||
final Path logPath = new Path("hdfs://localhost:8020/logs");
|
||||
builder.withLogPath(logPath).withVerboseLog(true);
|
||||
Assert.assertTrue(builder.build().shouldVerboseLog());
|
||||
}
|
||||
}
|
||||
|
@ -255,7 +255,13 @@ public void testCopyWithAppend() throws Exception {
|
||||
context.getConfiguration().setBoolean(
|
||||
DistCpOptionSwitch.APPEND.getConfigLabel(), true);
|
||||
copyMapper.setup(context);
|
||||
|
||||
int numFiles = 0;
|
||||
for (Path path: pathList) {
|
||||
if (fs.getFileStatus(path).isFile()) {
|
||||
numFiles++;
|
||||
}
|
||||
|
||||
copyMapper.map(new Text(DistCpUtils.getRelativePath(new Path(SOURCE_PATH), path)),
|
||||
new CopyListingFileStatus(cluster.getFileSystem().getFileStatus(
|
||||
path)), context);
|
||||
@ -266,7 +272,7 @@ public void testCopyWithAppend() throws Exception {
|
||||
Assert.assertEquals(nFiles * DEFAULT_FILE_SIZE * 2, stubContext
|
||||
.getReporter().getCounter(CopyMapper.Counter.BYTESCOPIED)
|
||||
.getValue());
|
||||
Assert.assertEquals(pathList.size(), stubContext.getReporter().
|
||||
Assert.assertEquals(numFiles, stubContext.getReporter().
|
||||
getCounter(CopyMapper.Counter.COPY).getValue());
|
||||
}
|
||||
|
||||
@ -295,7 +301,15 @@ private void testCopy(boolean preserveChecksum) throws Exception {
|
||||
|
||||
copyMapper.setup(context);
|
||||
|
||||
for (Path path: pathList) {
|
||||
int numFiles = 0;
|
||||
int numDirs = 0;
|
||||
for (Path path : pathList) {
|
||||
if (fs.getFileStatus(path).isDirectory()) {
|
||||
numDirs++;
|
||||
} else {
|
||||
numFiles++;
|
||||
}
|
||||
|
||||
copyMapper.map(
|
||||
new Text(DistCpUtils.getRelativePath(new Path(SOURCE_PATH), path)),
|
||||
new CopyListingFileStatus(fs.getFileStatus(path)), context);
|
||||
@ -303,8 +317,10 @@ private void testCopy(boolean preserveChecksum) throws Exception {
|
||||
|
||||
// Check that the maps worked.
|
||||
verifyCopy(fs, preserveChecksum);
|
||||
Assert.assertEquals(pathList.size(), stubContext.getReporter()
|
||||
Assert.assertEquals(numFiles, stubContext.getReporter()
|
||||
.getCounter(CopyMapper.Counter.COPY).getValue());
|
||||
Assert.assertEquals(numDirs, stubContext.getReporter()
|
||||
.getCounter(CopyMapper.Counter.DIR_COPY).getValue());
|
||||
if (!preserveChecksum) {
|
||||
Assert.assertEquals(nFiles * DEFAULT_FILE_SIZE, stubContext
|
||||
.getReporter().getCounter(CopyMapper.Counter.BYTESCOPIED)
|
||||
@ -1118,4 +1134,81 @@ private void testPreserveUserGroupImpl(boolean preserve){
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVerboseLogging() throws Exception {
|
||||
deleteState();
|
||||
createSourceData();
|
||||
|
||||
FileSystem fs = cluster.getFileSystem();
|
||||
CopyMapper copyMapper = new CopyMapper();
|
||||
StubContext stubContext = new StubContext(getConfiguration(), null, 0);
|
||||
Mapper<Text, CopyListingFileStatus, Text, Text>.Context context
|
||||
= stubContext.getContext();
|
||||
copyMapper.setup(context);
|
||||
|
||||
int numFiles = 0;
|
||||
for (Path path : pathList) {
|
||||
if (fs.getFileStatus(path).isFile()) {
|
||||
numFiles++;
|
||||
}
|
||||
|
||||
copyMapper.map(
|
||||
new Text(DistCpUtils.getRelativePath(new Path(SOURCE_PATH), path)),
|
||||
new CopyListingFileStatus(fs.getFileStatus(path)), context);
|
||||
}
|
||||
|
||||
// Check that the maps worked.
|
||||
Assert.assertEquals(numFiles, stubContext.getReporter()
|
||||
.getCounter(CopyMapper.Counter.COPY).getValue());
|
||||
|
||||
testCopyingExistingFiles(fs, copyMapper, context);
|
||||
// verify the verbose log
|
||||
// we shouldn't print verbose log since this option is disabled
|
||||
for (Text value : stubContext.getWriter().values()) {
|
||||
Assert.assertTrue(!value.toString().startsWith("FILE_COPIED:"));
|
||||
Assert.assertTrue(!value.toString().startsWith("FILE_SKIPPED:"));
|
||||
}
|
||||
|
||||
// test with verbose logging
|
||||
deleteState();
|
||||
createSourceData();
|
||||
|
||||
stubContext = new StubContext(getConfiguration(), null, 0);
|
||||
context = stubContext.getContext();
|
||||
copyMapper.setup(context);
|
||||
|
||||
// enables verbose logging
|
||||
context.getConfiguration().setBoolean(
|
||||
DistCpOptionSwitch.VERBOSE_LOG.getConfigLabel(), true);
|
||||
copyMapper.setup(context);
|
||||
|
||||
for (Path path : pathList) {
|
||||
copyMapper.map(
|
||||
new Text(DistCpUtils.getRelativePath(new Path(SOURCE_PATH), path)),
|
||||
new CopyListingFileStatus(fs.getFileStatus(path)), context);
|
||||
}
|
||||
|
||||
Assert.assertEquals(numFiles, stubContext.getReporter()
|
||||
.getCounter(CopyMapper.Counter.COPY).getValue());
|
||||
|
||||
// verify the verbose log of COPY log
|
||||
int numFileCopied = 0;
|
||||
for (Text value : stubContext.getWriter().values()) {
|
||||
if (value.toString().startsWith("FILE_COPIED:")) {
|
||||
numFileCopied++;
|
||||
}
|
||||
}
|
||||
Assert.assertEquals(numFiles, numFileCopied);
|
||||
|
||||
// verify the verbose log of SKIP log
|
||||
int numFileSkipped = 0;
|
||||
testCopyingExistingFiles(fs, copyMapper, context);
|
||||
for (Text value : stubContext.getWriter().values()) {
|
||||
if (value.toString().startsWith("FILE_SKIPPED:")) {
|
||||
numFileSkipped++;
|
||||
}
|
||||
}
|
||||
Assert.assertEquals(numFiles, numFileSkipped);
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@
|
||||
CounterGroupName= distcp
|
||||
|
||||
COPY.name= Files copied
|
||||
DIR_COPY.name= Directories copied
|
||||
SKIP.name= Files skipped
|
||||
FAIL.name= Files failed
|
||||
BYTESCOPIED.name= Bytes copied
|
||||
|
Loading…
Reference in New Issue
Block a user