This is an automated email from the ASF dual-hosted git repository.
steveloughran pushed a commit to branch branch-3.5
in repository https://gitbox.apache.org/repos/asf/hadoop.git
The following commit(s) were added to refs/heads/branch-3.5 by this push:
new 4c6b5111ed9 HDFS-17593. Allow setting block locations when opening
streams (#8561) (#8570)
4c6b5111ed9 is described below
commit 4c6b5111ed9fbfc762a8c79671a3ea2ca02b68ec
Author: boroknagyz <[email protected]>
AuthorDate: Tue Jun 30 18:50:28 2026 +0200
HDFS-17593. Allow setting block locations when opening streams (#8561)
(#8570)
Adds support for opening HDFS files with pre-fetched block locations,
eliminating redundant NameNode RPCs when callers already have cached
block metadata.
To achieve this, use the openFile()/openPath() builder methods and pass
in a filestatus; if it is an HdfsFileStatus the locations are picked up.
Contributed by Zoltan Borok-Nagy , with assistance Claude Opus 4.6
(cherry picked from commit 62daf8933ad7c66f463eb4eb2ac2dafef4b5186a)
---
.../java/org/apache/hadoop/hdfs/DFSClient.java | 20 ++
.../apache/hadoop/hdfs/DistributedFileSystem.java | 90 +++++
.../src/main/native/libhdfs/hdfs.c | 73 ++++
.../src/main/native/libhdfs/include/hdfs/hdfs.h | 22 +-
.../src/main/native/libhdfspp/tests/hdfs_shim.c | 15 +
.../libhdfspp/tests/libhdfs_wrapper_defines.h | 1 +
.../libhdfspp/tests/libhdfs_wrapper_undefs.h | 1 +
.../libhdfspp/tests/libhdfspp_wrapper_defines.h | 1 +
.../hadoop/hdfs/TestOpenFileWithLocatedBlocks.java | 394 +++++++++++++++++++++
9 files changed, 614 insertions(+), 3 deletions(-)
diff --git
a/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/DFSClient.java
b/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/DFSClient.java
index cbe7516b0ed..9a2b0e5fd5c 100755
---
a/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/DFSClient.java
+++
b/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/DFSClient.java
@@ -1078,6 +1078,26 @@ public DFSInputStream open(String src, int buffersize,
boolean verifyChecksum)
}
}
+ /**
+ * Create an input stream using pre-fetched block locations, skipping the
+ * NameNode RPC to get block locations.
+ * @param src file name
+ * @param buffersize ignored
+ * @param verifyChecksum verify checksums before returning data to client
+ * @param locatedBlocks pre-fetched block locations for this file
+ * @return an input stream for reading the file
+ * @throws IOException on I/O error
+ */
+ public DFSInputStream open(String src, int buffersize, boolean
verifyChecksum,
+ LocatedBlocks locatedBlocks) throws IOException {
+ Preconditions.checkArgument(locatedBlocks != null,
+ "null locatedBlocks");
+ checkOpen();
+ try (TraceScope ignored = newPathTraceScope("newDFSInputStream", src)) {
+ return openInternal(locatedBlocks, src, verifyChecksum);
+ }
+ }
+
/**
* Create an input stream from the {@link HdfsPathHandle} if the
* constraints encoded from {@link
diff --git
a/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/DistributedFileSystem.java
b/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/DistributedFileSystem.java
index dac205158d0..dd8b35de905 100644
---
a/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/DistributedFileSystem.java
+++
b/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/DistributedFileSystem.java
@@ -69,7 +69,9 @@
import org.apache.hadoop.fs.UnresolvedLinkException;
import org.apache.hadoop.fs.UnsupportedFileSystemException;
import org.apache.hadoop.fs.XAttrSetFlag;
+import org.apache.hadoop.fs.impl.AbstractFSBuilderImpl;
import org.apache.hadoop.fs.impl.FileSystemMultipartUploaderBuilder;
+import org.apache.hadoop.fs.impl.OpenFileParameters;
import org.apache.hadoop.fs.permission.AclEntry;
import org.apache.hadoop.fs.permission.AclStatus;
import org.apache.hadoop.fs.permission.FsAction;
@@ -115,6 +117,7 @@
import org.apache.hadoop.net.NetUtils;
import org.apache.hadoop.security.token.Token;
import org.apache.hadoop.security.token.DelegationTokenIssuer;
+import org.apache.hadoop.util.LambdaUtils;
import org.apache.hadoop.util.Lists;
import org.apache.hadoop.util.Progressable;
import org.slf4j.Logger;
@@ -123,6 +126,8 @@
import javax.annotation.Nonnull;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.io.InputStream;
+import java.util.concurrent.CompletableFuture;
import java.net.InetSocketAddress;
import java.net.URI;
import java.util.ArrayList;
@@ -378,6 +383,91 @@ public FSDataInputStream open(PathHandle fd, int
bufferSize)
return dfs.createWrappedInputStream(dfsis);
}
+ @Override
+ protected CompletableFuture<FSDataInputStream> openFileWithOptions(
+ final Path path,
+ final OpenFileParameters parameters) throws IOException {
+ AbstractFSBuilderImpl.rejectUnknownMandatoryKeys(
+ parameters.getMandatoryKeys(),
+ Options.OpenFileOptions.FS_OPTION_OPENFILE_STANDARD_OPTIONS,
+ "for " + path);
+ statistics.incrementReadOps(1);
+ storageStatistics.incrementOpCounter(OpType.OPEN);
+ final Path absF = fixRelativePart(path);
+ return LambdaUtils.eval(new CompletableFuture<>(), () -> {
+ LocatedBlocks locatedBlocks =
+ getLocatedBlocksFromStatus(absF, parameters.getStatus());
+ final DFSInputStream dfsis;
+ if (locatedBlocks != null) {
+ dfsis = dfs.open(getPathName(absF), parameters.getBufferSize(),
+ verifyChecksum, locatedBlocks);
+ } else {
+ dfsis = dfs.open(getPathName(absF), parameters.getBufferSize(),
+ verifyChecksum);
+ }
+ try {
+ return dfs.createWrappedInputStream(dfsis);
+ } catch (IOException e) {
+ dfsis.close();
+ throw e;
+ }
+ });
+ }
+
+ private static LocatedBlocks getLocatedBlocksFromStatus(
+ Path path, FileStatus status) {
+ if (status instanceof HdfsLocatedFileStatus locatedFileStatus) {
+ Preconditions.checkArgument(
+ path.getName().equals(status.getPath().getName()),
+ "Filename mismatch between file being opened %s"
+ + " and supplied status %s",
+ path, status.getPath());
+ return locatedFileStatus.getLocatedBlocks();
+ }
+ return null;
+ }
+
+ /**
+ * Create a new input stream for the same file as an existing stream,
+ * reusing its cached block locations to avoid a NameNode RPC.
+ * The returned stream is independent (its own position, buffers, etc.)
+ * but shares the same block location metadata.
+ *
+ * @param existing an open input stream obtained from this filesystem
+ * @return a new independent input stream for the same file
+ * @throws IOException if the stream cannot be cloned
+ */
+ public FSDataInputStream cloneDataInputStream(FSDataInputStream existing)
+ throws IOException {
+ statistics.incrementReadOps(1);
+ storageStatistics.incrementOpCounter(OpType.OPEN);
+ InputStream wrapped = existing.getWrappedStream();
+ DFSInputStream dfsis;
+ if (wrapped instanceof DFSInputStream dfsIn) {
+ dfsis = dfsIn;
+ } else if (wrapped instanceof
+ org.apache.hadoop.crypto.CryptoInputStream cryptoIn) {
+ dfsis = (DFSInputStream) cryptoIn.getWrappedStream();
+ } else {
+ throw new IOException("Cannot clone: underlying stream is "
+ + wrapped.getClass().getName() + ", not a DFSInputStream");
+ }
+ if (dfsis.getDFSClient() != dfs) {
+ throw new IOException(
+ "Cannot clone: stream belongs to a different DFSClient instance");
+ }
+ LocatedBlocks locatedBlocks = dfsis.getLocatedBlocks();
+ String src = dfsis.getSrc();
+ DFSInputStream clone = dfs.open(src,
+ dfs.getConf().getIoBufferSize(), verifyChecksum, locatedBlocks);
+ try {
+ return dfs.createWrappedInputStream(clone);
+ } catch (IOException e) {
+ clone.close();
+ throw e;
+ }
+ }
+
@Override
public String getErasureCodingPolicyName(FileStatus fileStatus) {
if (!(fileStatus instanceof HdfsFileStatus)) {
diff --git
a/hadoop-hdfs-project/hadoop-hdfs-native-client/src/main/native/libhdfs/hdfs.c
b/hadoop-hdfs-project/hadoop-hdfs-native-client/src/main/native/libhdfs/hdfs.c
index ed150925cdb..63e19bf3a4d 100644
---
a/hadoop-hdfs-project/hadoop-hdfs-native-client/src/main/native/libhdfs/hdfs.c
+++
b/hadoop-hdfs-project/hadoop-hdfs-native-client/src/main/native/libhdfs/hdfs.c
@@ -1885,6 +1885,79 @@ int hdfsCloseFile(hdfsFS fs, hdfsFile file)
return 0;
}
+hdfsFile hdfsCloneFile(hdfsFS fs, hdfsFile file)
+{
+ // JAVA EQUIVALENT:
+ // ((DistributedFileSystem) fs).cloneDataInputStream(file)
+ int ret = 0;
+ jthrowable jthr;
+ jvalue jVal;
+ jobject jFS = (jobject)fs;
+ jobject jClonedStream = NULL;
+ hdfsFile clonedFile = NULL;
+
+ JNIEnv* env = getJNIEnv();
+ if (env == NULL) {
+ errno = EINTERNAL;
+ return NULL;
+ }
+
+ if (!file || file->type != HDFS_STREAM_INPUT) {
+ fprintf(stderr, "hdfsCloneFile: can only clone input streams\n");
+ errno = EINVAL;
+ return NULL;
+ }
+
+ if (!(*env)->IsInstanceOf(env, jFS,
+ getJclass(JC_DISTRIBUTED_FILE_SYSTEM))) {
+ fprintf(stderr,
+ "hdfsCloneFile: not a DistributedFileSystem\n");
+ errno = ENOTSUP;
+ return NULL;
+ }
+
+ jthr = invokeMethod(env, &jVal, INSTANCE, jFS,
+ JC_DISTRIBUTED_FILE_SYSTEM, "cloneDataInputStream",
+ JMETHOD1(JPARAM(HADOOP_FSDISTRM), JPARAM(HADOOP_FSDISTRM)),
+ file->file);
+ if (jthr) {
+ ret = printExceptionAndFree(env, jthr, PRINT_EXC_ALL,
+ "hdfsCloneFile: DistributedFileSystem#cloneDataInputStream");
+ goto done;
+ }
+ jClonedStream = jVal.l;
+
+ clonedFile = calloc(1, sizeof(struct hdfsFile_internal));
+ if (!clonedFile) {
+ fprintf(stderr, "hdfsCloneFile: OOM\n");
+ ret = ENOMEM;
+ goto done;
+ }
+ clonedFile->file = (*env)->NewGlobalRef(env, jClonedStream);
+ if (!clonedFile->file) {
+ ret = printPendingExceptionAndFree(env, PRINT_EXC_ALL,
+ "hdfsCloneFile: NewGlobalRef");
+ goto done;
+ }
+ clonedFile->type = HDFS_STREAM_INPUT;
+ clonedFile->flags = 0;
+ setFileFlagCapabilities(clonedFile, jClonedStream);
+
+done:
+ destroyLocalReference(env, jClonedStream);
+ if (ret) {
+ if (clonedFile) {
+ if (clonedFile->file) {
+ (*env)->DeleteGlobalRef(env, clonedFile->file);
+ }
+ free(clonedFile);
+ }
+ errno = ret;
+ return NULL;
+ }
+ return clonedFile;
+}
+
int hdfsExists(hdfsFS fs, const char *path)
{
JNIEnv *env = getJNIEnv();
diff --git
a/hadoop-hdfs-project/hadoop-hdfs-native-client/src/main/native/libhdfs/include/hdfs/hdfs.h
b/hadoop-hdfs-project/hadoop-hdfs-native-client/src/main/native/libhdfs/include/hdfs/hdfs.h
index eba50ff6eb2..62be5a967fa 100644
---
a/hadoop-hdfs-project/hadoop-hdfs-native-client/src/main/native/libhdfs/include/hdfs/hdfs.h
+++
b/hadoop-hdfs-project/hadoop-hdfs-native-client/src/main/native/libhdfs/include/hdfs/hdfs.h
@@ -685,11 +685,27 @@ extern "C" {
int hdfsCloseFile(hdfsFS fs, hdfsFile file);
- /**
- * hdfsExists - Checks if a given path exsits on the filesystem
+ /**
+ * hdfsCloneFile - Create a new independent file handle for the same file
+ * as an existing handle, reusing cached block locations to avoid a
+ * NameNode RPC. The cloned handle has its own read position and buffers.
+ * Only works for input (read) streams on a DistributedFileSystem.
+ *
+ * @param fs The configured filesystem handle.
+ * @param file An open input file handle to clone.
+ * @return Returns a new file handle on success, NULL on error.
+ * On error, errno will be set appropriately.
+ * The returned handle must be closed with hdfsCloseFile.
+ */
+ LIBHDFS_EXTERNAL
+ hdfsFile hdfsCloneFile(hdfsFS fs, hdfsFile file);
+
+
+ /**
+ * hdfsExists - Checks if a given path exists on the filesystem
* @param fs The configured filesystem handle.
* @param path The path to look for
- * @return Returns 0 on success, -1 on error.
+ * @return Returns 0 on success, -1 on error.
*/
LIBHDFS_EXTERNAL
int hdfsExists(hdfsFS fs, const char *path);
diff --git
a/hadoop-hdfs-project/hadoop-hdfs-native-client/src/main/native/libhdfspp/tests/hdfs_shim.c
b/hadoop-hdfs-project/hadoop-hdfs-native-client/src/main/native/libhdfspp/tests/hdfs_shim.c
index ad8ad712c9d..61c3b81cd6a 100644
---
a/hadoop-hdfs-project/hadoop-hdfs-native-client/src/main/native/libhdfspp/tests/hdfs_shim.c
+++
b/hadoop-hdfs-project/hadoop-hdfs-native-client/src/main/native/libhdfspp/tests/hdfs_shim.c
@@ -331,6 +331,21 @@ int hdfsCloseFile(hdfsFS fs, hdfsFile file) {
return ret;
}
+hdfsFile hdfsCloneFile(hdfsFS fs, hdfsFile file) {
+ libhdfs_hdfsFile clonedRep =
+ libhdfs_hdfsCloneFile(fs->libhdfsRep, file->libhdfsRep);
+ if (!clonedRep) {
+ return NULL;
+ }
+ hdfsFile cloned = calloc(1, sizeof(struct hdfsFile_internal));
+ if (!cloned) {
+ return NULL;
+ }
+ cloned->libhdfsRep = clonedRep;
+ cloned->libhdfsppRep = NULL;
+ return cloned;
+}
+
int hdfsExists(hdfsFS fs, const char *path) {
return libhdfspp_hdfsExists(fs->libhdfsppRep, path);
}
diff --git
a/hadoop-hdfs-project/hadoop-hdfs-native-client/src/main/native/libhdfspp/tests/libhdfs_wrapper_defines.h
b/hadoop-hdfs-project/hadoop-hdfs-native-client/src/main/native/libhdfspp/tests/libhdfs_wrapper_defines.h
index 16574414255..c9f8dfe38ff 100644
---
a/hadoop-hdfs-project/hadoop-hdfs-native-client/src/main/native/libhdfspp/tests/libhdfs_wrapper_defines.h
+++
b/hadoop-hdfs-project/hadoop-hdfs-native-client/src/main/native/libhdfspp/tests/libhdfs_wrapper_defines.h
@@ -59,6 +59,7 @@
#define hdfsTruncateFile libhdfs_hdfsTruncateFile
#define hdfsUnbufferFile libhdfs_hdfsUnbufferFile
#define hdfsCloseFile libhdfs_hdfsCloseFile
+#define hdfsCloneFile libhdfs_hdfsCloneFile
#define hdfsExists libhdfs_hdfsExists
#define hdfsSeek libhdfs_hdfsSeek
#define hdfsTell libhdfs_hdfsTell
diff --git
a/hadoop-hdfs-project/hadoop-hdfs-native-client/src/main/native/libhdfspp/tests/libhdfs_wrapper_undefs.h
b/hadoop-hdfs-project/hadoop-hdfs-native-client/src/main/native/libhdfspp/tests/libhdfs_wrapper_undefs.h
index d84b8ba2875..5f6aa3aa1c9 100644
---
a/hadoop-hdfs-project/hadoop-hdfs-native-client/src/main/native/libhdfspp/tests/libhdfs_wrapper_undefs.h
+++
b/hadoop-hdfs-project/hadoop-hdfs-native-client/src/main/native/libhdfspp/tests/libhdfs_wrapper_undefs.h
@@ -59,6 +59,7 @@
#undef hdfsTruncateFile
#undef hdfsUnbufferFile
#undef hdfsCloseFile
+#undef hdfsCloneFile
#undef hdfsExists
#undef hdfsSeek
#undef hdfsTell
diff --git
a/hadoop-hdfs-project/hadoop-hdfs-native-client/src/main/native/libhdfspp/tests/libhdfspp_wrapper_defines.h
b/hadoop-hdfs-project/hadoop-hdfs-native-client/src/main/native/libhdfspp/tests/libhdfspp_wrapper_defines.h
index 0a6d987409f..da775c054a7 100644
---
a/hadoop-hdfs-project/hadoop-hdfs-native-client/src/main/native/libhdfspp/tests/libhdfspp_wrapper_defines.h
+++
b/hadoop-hdfs-project/hadoop-hdfs-native-client/src/main/native/libhdfspp/tests/libhdfspp_wrapper_defines.h
@@ -59,6 +59,7 @@
#define hdfsTruncateFile libhdfspp_hdfsTruncateFile
#define hdfsUnbufferFile libhdfspp_hdfsUnbufferFile
#define hdfsCloseFile libhdfspp_hdfsCloseFile
+#define hdfsCloneFile libhdfspp_hdfsCloneFile
#define hdfsExists libhdfspp_hdfsExists
#define hdfsSeek libhdfspp_hdfsSeek
#define hdfsTell libhdfspp_hdfsTell
diff --git
a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestOpenFileWithLocatedBlocks.java
b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestOpenFileWithLocatedBlocks.java
new file mode 100644
index 00000000000..66fe7db497f
--- /dev/null
+++
b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestOpenFileWithLocatedBlocks.java
@@ -0,0 +1,394 @@
+/**
+ * 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;
+
+import static org.apache.hadoop.test.MetricsAsserts.getMetrics;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.crypto.CryptoInputStream;
+import org.apache.hadoop.crypto.key.JavaKeyStoreProvider;
+import org.apache.hadoop.fs.CommonConfigurationKeysPublic;
+import org.apache.hadoop.fs.FSDataInputStream;
+import org.apache.hadoop.fs.FileStatus;
+import org.apache.hadoop.fs.FileSystemTestHelper;
+import org.apache.hadoop.fs.LocatedFileStatus;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.fs.RemoteIterator;
+import org.apache.hadoop.fs.contract.ContractTestUtils;
+import org.apache.hadoop.hdfs.client.CreateEncryptionZoneFlag;
+import org.apache.hadoop.hdfs.client.HdfsAdmin;
+import org.apache.hadoop.hdfs.protocol.HdfsLocatedFileStatus;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests that opening a file with pre-fetched block locations via the
+ * {@code openFile().withFileStatus()} builder API skips the NameNode RPC
+ * for block locations.
+ */
+public class TestOpenFileWithLocatedBlocks {
+ private static final String NN_METRICS = "NameNodeActivity";
+ private static final int BLOCK_SIZE = 1024;
+ private static final short REPLICATION = 1;
+ private static final String TEST_KEY = "test_key";
+ private static final EnumSet<CreateEncryptionZoneFlag> NO_TRASH =
+ EnumSet.of(CreateEncryptionZoneFlag.NO_TRASH);
+
+ private static MiniDFSCluster cluster;
+ private static DistributedFileSystem fs;
+ private static Configuration conf;
+ private static Path ezPath;
+
+ @BeforeAll
+ public static void setupCluster() throws Exception {
+ conf = new HdfsConfiguration();
+ conf.setLong(DFSConfigKeys.DFS_BLOCK_SIZE_KEY, BLOCK_SIZE);
+ FileSystemTestHelper fsHelper = new FileSystemTestHelper();
+ File testRootDir = new File(fsHelper.getTestRootDir()).getAbsoluteFile();
+ conf.set(CommonConfigurationKeysPublic.HADOOP_SECURITY_KEY_PROVIDER_PATH,
+ JavaKeyStoreProvider.SCHEME_NAME + "://file" +
+ new Path(testRootDir.toString(), "test.jks").toUri());
+ cluster = new MiniDFSCluster.Builder(conf)
+ .numDataNodes(1)
+ .build();
+ cluster.waitActive();
+ fs = cluster.getFileSystem();
+ createEncryptionZone();
+ }
+
+ private static void createEncryptionZone() throws Exception {
+ DFSTestUtil.createKey(TEST_KEY, cluster, conf);
+ fs.getClient().setKeyProvider(
+ cluster.getNameNode().getNamesystem().getProvider());
+ ezPath = new Path("/ez");
+ fs.mkdirs(ezPath);
+ new HdfsAdmin(cluster.getURI(), conf)
+ .createEncryptionZone(ezPath, TEST_KEY, NO_TRASH);
+ }
+
+ @AfterAll
+ public static void teardownCluster() {
+ if (cluster != null) {
+ cluster.shutdown();
+ cluster = null;
+ }
+ }
+
+ private Path createFile(String name, byte[] data) throws IOException {
+ Path path = new Path("/" + name);
+ ContractTestUtils.createFile(fs, path, true, data);
+ return path;
+ }
+
+ @Test
+ public void testOpenFileWithLocatedFileStatus() throws Exception {
+ byte[] data = ContractTestUtils.dataset(BLOCK_SIZE * 2, 'a', 26);
+ Path path = createFile("testfile", data);
+
+ RemoteIterator<LocatedFileStatus> iter = fs.listLocatedStatus(path);
+ HdfsLocatedFileStatus locatedStatus =
+ (HdfsLocatedFileStatus) iter.next();
+
+ long countBefore = getLong(getMetrics(NN_METRICS),
+ "GetBlockLocations");
+
+ try (FSDataInputStream in = fs.openFile(path)
+ .withFileStatus(locatedStatus)
+ .build()
+ .get()) {
+ ContractTestUtils.verifyRead(in, data, 0, data.length);
+ }
+
+ long countAfter = getLong(getMetrics(NN_METRICS),
+ "GetBlockLocations");
+ assertEquals(countBefore, countAfter,
+ "Opening with pre-fetched locations should not trigger " +
+ "GetBlockLocations RPC");
+ }
+
+ @Test
+ public void testOpenFileWithoutStatusFallsBack() throws Exception {
+ byte[] data = ContractTestUtils.dataset(BLOCK_SIZE, 'b', 26);
+ Path path = createFile("testfile_no_status", data);
+
+ long countBefore = getLong(getMetrics(NN_METRICS),
+ "GetBlockLocations");
+
+ try (FSDataInputStream in = fs.openFile(path)
+ .build()
+ .get()) {
+ ContractTestUtils.verifyRead(in, data, 0, data.length);
+ }
+
+ long countAfter = getLong(getMetrics(NN_METRICS),
+ "GetBlockLocations");
+ assertEquals(countBefore + 1, countAfter,
+ "Opening without status should trigger GetBlockLocations RPC");
+ }
+
+ @Test
+ public void testOpenFileWithPlainFileStatusFallsBack() throws Exception {
+ byte[] data = ContractTestUtils.dataset(BLOCK_SIZE, 'c', 26);
+ Path path = createFile("testfile_plain_status", data);
+
+ FileStatus plainStatus = fs.getFileStatus(path);
+
+ long countBefore = getLong(getMetrics(NN_METRICS),
+ "GetBlockLocations");
+
+ try (FSDataInputStream in = fs.openFile(path)
+ .withFileStatus(plainStatus)
+ .build()
+ .get()) {
+ ContractTestUtils.verifyRead(in, data, 0, data.length);
+ }
+
+ long countAfter = getLong(getMetrics(NN_METRICS),
+ "GetBlockLocations");
+ assertEquals(countBefore + 1, countAfter,
+ "Opening with plain FileStatus should trigger " +
+ "GetBlockLocations RPC");
+ }
+
+ @Test
+ public void testCloneDataInputStream() throws Exception {
+ byte[] data = ContractTestUtils.dataset(BLOCK_SIZE * 3, 'd', 26);
+ Path path = createFile("testfile_clone", data);
+
+ try (FSDataInputStream original = fs.open(path)) {
+ long countBefore = getLong(getMetrics(NN_METRICS),
+ "GetBlockLocations");
+
+ try (FSDataInputStream cloned = fs.cloneDataInputStream(original)) {
+ long countAfter = getLong(getMetrics(NN_METRICS),
+ "GetBlockLocations");
+ assertEquals(countBefore, countAfter,
+ "Cloning should not trigger GetBlockLocations RPC");
+
+ ContractTestUtils.verifyRead(cloned, data, 0, data.length);
+ }
+
+ ContractTestUtils.verifyRead(original, data, 0, data.length);
+ }
+ }
+
+ @Test
+ public void testCloneDataInputStreamIndependentPosition() throws Exception {
+ byte[] data = ContractTestUtils.dataset(BLOCK_SIZE * 2, 'e', 26);
+ Path path = createFile("testfile_clone_pos", data);
+
+ try (FSDataInputStream original = fs.open(path)) {
+ byte[] buf1 = new byte[BLOCK_SIZE];
+ original.readFully(buf1);
+
+ try (FSDataInputStream cloned = fs.cloneDataInputStream(original)) {
+ assertEquals(0, cloned.getPos());
+ assertEquals(BLOCK_SIZE, original.getPos());
+
+ ContractTestUtils.verifyRead(cloned, data, 0, data.length);
+ }
+ }
+ }
+
+ @Test
+ public void testMultipleClonesFromSameHandle() throws Exception {
+ byte[] data = ContractTestUtils.dataset(BLOCK_SIZE * 2, 'f', 26);
+ Path path = createFile("testfile_multi_clone", data);
+
+ try (FSDataInputStream original = fs.open(path)) {
+ long countBefore = getLong(getMetrics(NN_METRICS),
+ "GetBlockLocations");
+
+ int numClones = 5;
+ FSDataInputStream[] clones = new FSDataInputStream[numClones];
+ for (int i = 0; i < numClones; i++) {
+ clones[i] = fs.cloneDataInputStream(original);
+ }
+
+ long countAfter = getLong(getMetrics(NN_METRICS),
+ "GetBlockLocations");
+ assertEquals(countBefore, countAfter,
+ "Multiple clones should not trigger GetBlockLocations RPC");
+
+ for (int i = 0; i < numClones; i++) {
+ ContractTestUtils.verifyRead(clones[i], data, 0, data.length);
+ clones[i].close();
+ }
+ }
+ }
+
+ @Test
+ public void testCloneOfClone() throws Exception {
+ byte[] data = ContractTestUtils.dataset(BLOCK_SIZE * 2, 'g', 26);
+ Path path = createFile("testfile_clone_of_clone", data);
+
+ try (FSDataInputStream original = fs.open(path)) {
+ long countBefore = getLong(getMetrics(NN_METRICS),
+ "GetBlockLocations");
+
+ try (FSDataInputStream clone1 = fs.cloneDataInputStream(original);
+ FSDataInputStream clone2 = fs.cloneDataInputStream(clone1)) {
+ long countAfter = getLong(getMetrics(NN_METRICS),
+ "GetBlockLocations");
+ assertEquals(countBefore, countAfter,
+ "Clone of clone should not trigger GetBlockLocations RPC");
+
+ ContractTestUtils.verifyRead(clone2, data, 0, data.length);
+ }
+ }
+ }
+
+ @Test
+ public void testConcurrentPreadFromClones() throws Exception {
+ byte[] data = ContractTestUtils.dataset(BLOCK_SIZE * 4, 'h', 26);
+ Path path = createFile("testfile_concurrent", data);
+
+ try (FSDataInputStream original = fs.open(path)) {
+ int numReaders = 4;
+ FSDataInputStream[] clones = new FSDataInputStream[numReaders];
+ for (int i = 0; i < numReaders; i++) {
+ clones[i] = fs.cloneDataInputStream(original);
+ }
+
+ ExecutorService executor = Executors.newFixedThreadPool(numReaders);
+ List<Future<byte[]>> futures = new ArrayList<>();
+ for (int i = 0; i < numReaders; i++) {
+ final int blockIdx = i;
+ final FSDataInputStream stream = clones[i];
+ futures.add(executor.submit(() -> {
+ byte[] buf = new byte[BLOCK_SIZE];
+ stream.readFully(blockIdx * BLOCK_SIZE, buf);
+ return buf;
+ }));
+ }
+
+ for (int i = 0; i < numReaders; i++) {
+ byte[] expected = new byte[BLOCK_SIZE];
+ System.arraycopy(data, i * BLOCK_SIZE, expected, 0, BLOCK_SIZE);
+ assertArrayEquals(expected, futures.get(i).get());
+ clones[i].close();
+ }
+ executor.shutdown();
+ }
+ }
+
+ @Test
+ public void testCloneAfterOriginalClosed() throws Exception {
+ byte[] data = ContractTestUtils.dataset(BLOCK_SIZE, 'i', 26);
+ Path path = createFile("testfile_clone_after_close", data);
+
+ FSDataInputStream cloned;
+ try (FSDataInputStream original = fs.open(path)) {
+ cloned = fs.cloneDataInputStream(original);
+ }
+ ContractTestUtils.verifyRead(cloned, data, 0, data.length);
+ cloned.close();
+ }
+
+ @Test
+ public void testCloneDataInputStreamEncrypted() throws Exception {
+ byte[] data = ContractTestUtils.dataset(BLOCK_SIZE * 2, 'j', 26);
+ Path path = new Path(ezPath, "encrypted_file");
+ ContractTestUtils.createFile(fs, path, true, data);
+
+ try (FSDataInputStream original = fs.open(path)) {
+ assertInstanceOf(CryptoInputStream.class, original.getWrappedStream(),
+ "Expected CryptoInputStream for encrypted file");
+
+ long countBefore = getLong(getMetrics(NN_METRICS),
+ "GetBlockLocations");
+
+ try (FSDataInputStream cloned = fs.cloneDataInputStream(original)) {
+ long countAfter = getLong(getMetrics(NN_METRICS),
+ "GetBlockLocations");
+ assertEquals(countBefore, countAfter,
+ "Cloning encrypted stream should not trigger "
+ + "GetBlockLocations RPC");
+
+ ContractTestUtils.verifyRead(cloned, data, 0, data.length);
+ }
+
+ ContractTestUtils.verifyRead(original, data, 0, data.length);
+ }
+ }
+
+ @Test
+ public void testOpenFileWithStatusEncrypted() throws Exception {
+ byte[] data = ContractTestUtils.dataset(BLOCK_SIZE * 2, 'k', 26);
+ Path path = new Path(ezPath, "encrypted_file2");
+ ContractTestUtils.createFile(fs, path, true, data);
+
+ RemoteIterator<LocatedFileStatus> iter = fs.listLocatedStatus(path);
+ HdfsLocatedFileStatus locatedStatus =
+ (HdfsLocatedFileStatus) iter.next();
+
+ long countBefore = getLong(getMetrics(NN_METRICS),
+ "GetBlockLocations");
+
+ try (FSDataInputStream in = fs.openFile(path)
+ .withFileStatus(locatedStatus)
+ .build()
+ .get()) {
+ ContractTestUtils.verifyRead(in, data, 0, data.length);
+ }
+
+ long countAfter = getLong(getMetrics(NN_METRICS),
+ "GetBlockLocations");
+ assertEquals(countBefore, countAfter,
+ "Opening encrypted file with pre-fetched locations should not "
+ + "trigger GetBlockLocations RPC");
+ }
+
+ @Test
+ public void testCloneEncryptedIndependentPosition() throws Exception {
+ byte[] data = ContractTestUtils.dataset(BLOCK_SIZE * 2, 'l', 26);
+ Path path = new Path(ezPath, "encrypted_file3");
+ ContractTestUtils.createFile(fs, path, true, data);
+
+ try (FSDataInputStream original = fs.open(path)) {
+ byte[] buf1 = new byte[BLOCK_SIZE];
+ original.readFully(buf1);
+ assertEquals(BLOCK_SIZE, original.getPos());
+
+ try (FSDataInputStream cloned = fs.cloneDataInputStream(original)) {
+ assertEquals(0, cloned.getPos());
+
+ ContractTestUtils.verifyRead(cloned, data, 0, data.length);
+ }
+ }
+ }
+
+ private static long getLong(
+ org.apache.hadoop.metrics2.MetricsRecordBuilder rb, String name) {
+ return org.apache.hadoop.test.MetricsAsserts.getLongCounter(name, rb);
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]