Skip to content

Commit 590c9c2

Browse files
authored
Report source lineage from HadoopFormatIO (#37265)
* Report source lineage from HadoopFormatIO * Extract shared code into FileSystems public API * A little refactoring * Fix import in HadoopFormatIO * check style * Implement lineage in HDFS and add tests * address review comments * Address review comments
1 parent 978c375 commit 590c9c2

3 files changed

Lines changed: 62 additions & 0 deletions

File tree

sdks/java/io/hadoop-file-system/src/main/java/org/apache/beam/sdk/io/hdfs/HadoopFileSystem.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import org.apache.beam.sdk.io.fs.MatchResult.Metadata;
3939
import org.apache.beam.sdk.io.fs.MatchResult.Status;
4040
import org.apache.beam.sdk.io.fs.MoveOptions;
41+
import org.apache.beam.sdk.metrics.Lineage;
4142
import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.annotations.VisibleForTesting;
4243
import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.collect.ImmutableList;
4344
import org.apache.hadoop.conf.Configuration;
@@ -336,6 +337,22 @@ protected String getScheme() {
336337
return scheme;
337338
}
338339

340+
@Override
341+
protected void reportLineage(HadoopResourceId resourceId, Lineage lineage, LineageLevel level) {
342+
URI uri = resourceId.toPath().toUri();
343+
ImmutableList.Builder<String> segments = ImmutableList.builder();
344+
if (uri.getAuthority() != null && !uri.getAuthority().isEmpty()) {
345+
segments.add(uri.getAuthority());
346+
}
347+
if (level != LineageLevel.TOP_LEVEL
348+
&& uri.getPath() != null
349+
&& !uri.getPath().isEmpty()
350+
&& !uri.getPath().equals("/")) {
351+
segments.add(uri.getPath());
352+
}
353+
lineage.add(scheme, segments.build(), "/");
354+
}
355+
339356
/** An adapter around {@link FSDataInputStream} that implements {@link SeekableByteChannel}. */
340357
private static class HadoopSeekableByteChannel implements SeekableByteChannel {
341358
private final FileStatus fileStatus;

sdks/java/io/hadoop-file-system/src/test/java/org/apache/beam/sdk/io/hdfs/HadoopFileSystemTest.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424
import static org.hamcrest.Matchers.hasSize;
2525
import static org.junit.Assert.assertArrayEquals;
2626
import static org.junit.Assert.assertEquals;
27+
import static org.mockito.Mockito.mock;
28+
import static org.mockito.Mockito.times;
29+
import static org.mockito.Mockito.verify;
2730

2831
import java.io.FileNotFoundException;
2932
import java.io.InputStream;
@@ -37,12 +40,14 @@
3740
import java.util.Collections;
3841
import java.util.List;
3942
import java.util.Objects;
43+
import org.apache.beam.sdk.io.FileSystem;
4044
import org.apache.beam.sdk.io.FileSystems;
4145
import org.apache.beam.sdk.io.TextIO;
4246
import org.apache.beam.sdk.io.fs.CreateOptions.StandardCreateOptions;
4347
import org.apache.beam.sdk.io.fs.MatchResult;
4448
import org.apache.beam.sdk.io.fs.MatchResult.Metadata;
4549
import org.apache.beam.sdk.io.fs.MatchResult.Status;
50+
import org.apache.beam.sdk.metrics.Lineage;
4651
import org.apache.beam.sdk.testing.ExpectedLogs;
4752
import org.apache.beam.sdk.testing.PAssert;
4853
import org.apache.beam.sdk.testing.TestPipeline;
@@ -481,6 +486,21 @@ public void testReadPipeline() throws Exception {
481486
p.run();
482487
}
483488

489+
@Test
490+
public void testReportLineage() {
491+
verifyLineage(
492+
"hdfs://namenode/path/to/file.txt", ImmutableList.of("namenode", "/path/to/file.txt"));
493+
verifyLineage("hdfs://namenode/", ImmutableList.of("namenode"));
494+
verifyLineage("hdfs://namenode", ImmutableList.of("namenode"));
495+
}
496+
497+
private void verifyLineage(String uri, List<String> expected) {
498+
HadoopResourceId resourceId = new HadoopResourceId(URI.create(uri));
499+
Lineage mockLineage = mock(Lineage.class);
500+
fileSystem.reportLineage(resourceId, mockLineage, FileSystem.LineageLevel.FILE);
501+
verify(mockLineage, times(1)).add("hdfs", expected, "/");
502+
}
503+
484504
private void create(String relativePath, byte[] contents) throws Exception {
485505
try (WritableByteChannel channel =
486506
fileSystem.create(

sdks/java/io/hadoop-format/src/main/java/org/apache/beam/sdk/io/hadoop/format/HadoopFormatIO.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import java.lang.reflect.InvocationTargetException;
3535
import java.math.BigDecimal;
3636
import java.math.BigInteger;
37+
import java.net.URI;
3738
import java.util.ArrayList;
3839
import java.util.Arrays;
3940
import java.util.HashMap;
@@ -54,6 +55,7 @@
5455
import org.apache.beam.sdk.io.BoundedSource;
5556
import org.apache.beam.sdk.io.hadoop.SerializableConfiguration;
5657
import org.apache.beam.sdk.io.hadoop.WritableCoder;
58+
import org.apache.beam.sdk.metrics.Lineage;
5759
import org.apache.beam.sdk.options.PipelineOptions;
5860
import org.apache.beam.sdk.transforms.Combine;
5961
import org.apache.beam.sdk.transforms.Create;
@@ -97,6 +99,7 @@
9799
import org.apache.hadoop.mapreduce.TaskAttemptID;
98100
import org.apache.hadoop.mapreduce.TaskID;
99101
import org.apache.hadoop.mapreduce.lib.db.DBConfiguration;
102+
import org.apache.hadoop.mapreduce.lib.input.FileSplit;
100103
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
101104
import org.apache.hadoop.mapreduce.task.JobContextImpl;
102105
import org.apache.hadoop.mapreduce.task.TaskAttemptContextImpl;
@@ -725,6 +728,7 @@ public List<BoundedSource<KV<K, V>>> split(long desiredBundleSizeBytes, Pipeline
725728
return ImmutableList.of(this);
726729
}
727730
computeSplitsIfNecessary();
731+
reportSourceLineage(inputSplits);
728732
LOG.info(
729733
"Generated {} splits. Size of first split is {} ",
730734
inputSplits.size(),
@@ -744,6 +748,27 @@ public List<BoundedSource<KV<K, V>>> split(long desiredBundleSizeBytes, Pipeline
744748
.collect(Collectors.toList());
745749
}
746750

751+
private void reportSourceLineage(final List<SerializableSplit> inputSplits) {
752+
for (SerializableSplit serializableSplit : inputSplits) {
753+
InputSplit split = serializableSplit.getSplit();
754+
if (split instanceof FileSplit) {
755+
URI uri = ((FileSplit) split).getPath().toUri();
756+
String scheme = uri.getScheme();
757+
if (scheme == null) {
758+
continue;
759+
}
760+
ImmutableList.Builder<String> segments = ImmutableList.builder();
761+
if (uri.getAuthority() != null && !uri.getAuthority().isEmpty()) {
762+
segments.add(uri.getAuthority());
763+
}
764+
if (uri.getPath() != null && !uri.getPath().isEmpty() && !uri.getPath().equals("/")) {
765+
segments.add(uri.getPath());
766+
}
767+
Lineage.getSources().add(scheme, segments.build(), "/");
768+
}
769+
}
770+
}
771+
747772
@Override
748773
public long getEstimatedSizeBytes(PipelineOptions po) throws Exception {
749774
if (inputSplit == null) {

0 commit comments

Comments
 (0)