This is an automated email from the ASF dual-hosted git repository.

jojochuang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ozone.git


The following commit(s) were added to refs/heads/master by this push:
     new 9ddbaa4e50e HDDS-15925. Remove redundant InfoBucket RPC from OFS 
getFileStatus by validating bucket layout server-side (#11226)
9ddbaa4e50e is described below

commit 9ddbaa4e50e7159ce9c55f44733d35921af0f674
Author: Andrey Yarovoy <[email protected]>
AuthorDate: Thu Sep 17 13:59:01 2026 -0400

    HDDS-15925. Remove redundant InfoBucket RPC from OFS getFileStatus by 
validating bucket layout server-side (#11226)
    
    Generated-by: Claude Code (Claude Opus 4.8)
---
 .../apache/hadoop/ozone/OzoneManagerVersion.java   |   7 +-
 .../ozone/client/protocol/ClientProtocol.java      |   9 +
 .../apache/hadoop/ozone/client/rpc/RpcClient.java  |   5 +
 .../ozone/AbstractRootedOzoneFileSystemTest.java   |  66 +++-
 .../fs/ozone/TestOfsGetFileStatusBenchmark.java    | 356 +++++++++++++++++++++
 .../org/apache/hadoop/ozone/om/TestOmAcls.java     |   4 +-
 .../hadoop/ozone/om/snapshot/OmSnapshotTests.java  |   2 +
 .../apache/hadoop/ozone/om/OmMetadataReader.java   |  13 +
 .../hadoop/ozone/om/TestOMMetadataReader.java      |  63 ++++
 .../ozone/BasicRootedOzoneClientAdapterImpl.java   |  44 ++-
 .../TestBasicRootedOzoneClientAdapterHeadOp.java   |  49 ++-
 .../hadoop/ozone/client/ClientProtocolStub.java    |   6 +
 12 files changed, 606 insertions(+), 18 deletions(-)

diff --git 
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneManagerVersion.java
 
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneManagerVersion.java
index a968dd9618e..9bd041c615c 100644
--- 
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneManagerVersion.java
+++ 
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneManagerVersion.java
@@ -61,7 +61,12 @@ public enum OzoneManagerVersion implements ComponentVersion {
   S3_BUCKET_TAGGING_API(13,
       "OzoneManager version that supports S3 bucket tagging APIs, such as "
           + "PutBucketTagging, GetBucketTagging, and DeleteBucketTagging"),
-    
+
+  GET_FILE_STATUS_REJECTS_OBS(14,
+      "OzoneManager version that rejects getFileStatus on OBJECT_STORE "
+          + "buckets server-side, so file system clients no longer need the "
+          + "client-side InfoBucket layout check"),
+
   FUTURE_VERSION(-1, "Used internally in the client when the server side is "
       + " newer and an unknown server version has arrived to the client.");
 
diff --git 
a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/protocol/ClientProtocol.java
 
b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/protocol/ClientProtocol.java
index 84b49e1b57e..3a31a111d6e 100644
--- 
a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/protocol/ClientProtocol.java
+++ 
b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/protocol/ClientProtocol.java
@@ -31,6 +31,7 @@
 import org.apache.hadoop.io.Text;
 import org.apache.hadoop.ozone.OzoneAcl;
 import org.apache.hadoop.ozone.OzoneFsServerDefaults;
+import org.apache.hadoop.ozone.OzoneManagerVersion;
 import org.apache.hadoop.ozone.client.BucketArgs;
 import org.apache.hadoop.ozone.client.OzoneBucket;
 import org.apache.hadoop.ozone.client.OzoneKey;
@@ -1063,6 +1064,14 @@ TenantUserList listUsersInTenant(String tenantId, String 
prefix)
    */
   OzoneFsServerDefaults getServerDefaults() throws IOException;
 
+  /**
+   * Returns the negotiated Ozone Manager version for the connected cluster.
+   * In an HA cluster this is the minimum version across all OMs, so callers
+   * can safely gate client behavior on new server-side features.
+   * @return the effective Ozone Manager version.
+   */
+  OzoneManagerVersion getOmVersion();
+
   /**
    * Get KMS client provider.
    * @return KMS client provider.
diff --git 
a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rpc/RpcClient.java
 
b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rpc/RpcClient.java
index 9c121bde4c8..92f4df2b7c9 100644
--- 
a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rpc/RpcClient.java
+++ 
b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rpc/RpcClient.java
@@ -2845,6 +2845,11 @@ public KeyProvider call() throws Exception {
     }
   }
 
+  @Override
+  public OzoneManagerVersion getOmVersion() {
+    return omVersion;
+  }
+
   @Override
   public OzoneFsServerDefaults getServerDefaults() throws IOException {
     long now = Time.monotonicNow();
diff --git 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/AbstractRootedOzoneFileSystemTest.java
 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/AbstractRootedOzoneFileSystemTest.java
index a0d09264adb..197d100d86c 100644
--- 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/AbstractRootedOzoneFileSystemTest.java
+++ 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/AbstractRootedOzoneFileSystemTest.java
@@ -1835,16 +1835,74 @@ void testBucketDefaultsShouldBeInheritedToFileForEC()
     assertEquals(ReplicationType.EC.name(), 
key.getReplicationConfig().getReplicationType().name());
   }
 
+  @Test
+  void testGetFileStatusUsesSingleOmRpc() throws Exception {
+    String keyName = "single-rpc-" + 
RandomStringUtils.secure().nextAlphabetic(5);
+    Path filePath = new Path(bucketPath, keyName);
+    ContractTestUtils.touch(fs, filePath);
+
+    OMMetrics metrics = getOMMetrics();
+    long bucketInfosBefore = metrics.getNumBucketInfos();
+    long getFileStatusBefore = metrics.getNumGetFileStatus();
+
+    FileStatus status = fs.getFileStatus(filePath);
+    assertTrue(status.isFile());
+
+    assertEquals(bucketInfosBefore, metrics.getNumBucketInfos(),
+        "getFileStatus must not trigger InfoBucket");
+    assertEquals(getFileStatusBefore + 1, metrics.getNumGetFileStatus());
+
+    long getFileStatusAfterFirst = metrics.getNumGetFileStatus();
+    fs.getFileStatus(filePath);
+    assertEquals(bucketInfosBefore, metrics.getNumBucketInfos());
+    assertEquals(getFileStatusAfterFirst + 1, metrics.getNumGetFileStatus());
+  }
+
+  @Test
+  void testGetFileStatusRejectsObsBucket() throws Exception {
+    OzoneBucket obsBucket =
+        DataTestUtil.createVolumeAndBucket(client, BucketLayout.OBJECT_STORE);
+    Path obsBucketPath = new Path(
+        new Path(OZONE_URI_DELIMITER, obsBucket.getVolumeName()),
+        obsBucket.getName());
+    String keyName = "obs-key-" + RandomStringUtils.secure().nextAlphabetic(5);
+    DataTestUtil.createKey(obsBucket, keyName,
+        "data".getBytes(StandardCharsets.UTF_8));
+    Path keyPath = new Path(obsBucketPath, keyName);
+
+    OMMetrics metrics = getOMMetrics();
+    long bucketInfosBefore = metrics.getNumBucketInfos();
+
+    IllegalArgumentException exception = 
assertThrows(IllegalArgumentException.class,
+        () -> fs.getFileStatus(keyPath));
+    assertThat(exception.getMessage()).contains(obsBucket.getName());
+    assertThat(exception.getMessage()).contains("OBJECT_STORE");
+    assertEquals(bucketInfosBefore, metrics.getNumBucketInfos(),
+        "getFileStatus must not trigger InfoBucket");
+  }
+
   @Test
   void testGetFileStatus() throws Exception {
     String volumeNameLocal = getRandomNonExistVolumeName();
     String bucketNameLocal = RandomStringUtils.secure().nextNumeric(5);
     Path volume = new Path("/" + volumeNameLocal);
     fs.mkdirs(volume);
-    assertThrows(OMException.class,
-        () -> fs.getFileStatus(new Path(volume, bucketNameLocal)));
-    // Cleanup
-    fs.delete(volume, false);
+    try {
+      FileNotFoundException exception = 
assertThrows(FileNotFoundException.class,
+          () -> fs.getFileStatus(new Path(volume, bucketNameLocal)));
+      assertThat(exception.getMessage()).contains("Bucket doesn't exist");
+    } finally {
+      fs.delete(volume, false);
+    }
+  }
+
+  @Test
+  void testGetFileStatusMissingFile() throws Exception {
+    Path missingFile = new Path(bucketPath, "missing-file-" +
+        RandomStringUtils.secure().nextAlphanumeric(5));
+    FileNotFoundException exception = assertThrows(FileNotFoundException.class,
+        () -> fs.getFileStatus(missingFile));
+    assertThat(exception.getMessage()).contains("No such file or directory");
   }
 
   @Test
diff --git 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestOfsGetFileStatusBenchmark.java
 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestOfsGetFileStatusBenchmark.java
new file mode 100644
index 00000000000..37987e2b84c
--- /dev/null
+++ 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestOfsGetFileStatusBenchmark.java
@@ -0,0 +1,356 @@
+/*
+ * 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.ozone;
+
+import static 
org.apache.hadoop.fs.CommonConfigurationKeysPublic.FS_DEFAULT_NAME_KEY;
+import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_ADDRESS_KEY;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicReference;
+import org.apache.hadoop.fs.FileSystem;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.fs.contract.ContractTestUtils;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.hdds.utils.IOUtils;
+import org.apache.hadoop.ozone.MiniOzoneCluster;
+import org.apache.hadoop.ozone.OzoneConsts;
+import org.apache.hadoop.ozone.client.BucketArgs;
+import org.apache.hadoop.ozone.client.ObjectStore;
+import org.apache.hadoop.ozone.client.OzoneClient;
+import org.apache.hadoop.ozone.client.OzoneVolume;
+import org.apache.hadoop.ozone.om.OMConfigKeys;
+import org.apache.hadoop.ozone.om.OMMetrics;
+import org.apache.hadoop.ozone.om.helpers.BucketLayout;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Benchmark for OFS {@code getFileStatus} on non-snapshot paths (HDDS-15925).
+ *
+ * <p>Runs a repeated-access getFileStatus workload against one
+ * {@link MiniOzoneCluster} using the default client configuration, and reports
+ * the InfoBucket RPCs, getFileStatus RPCs, per-call latency (mean/p50/p90/p99/
+ * max) and throughput measured by OM. The workload touches {@link 
#NUM_BUCKETS}
+ * buckets {@link #ACCESSES_PER_BUCKET} times each in a shuffled order, the 
way a
+ * real workload repeatedly stats files across many buckets.
+ *
+ * <p>The identical test runs on both this branch and the baseline
+ * ({@code master}) code. The before/after comparison is what OM reports for 
the
+ * same workload:
+ * <ul>
+ *   <li>baseline: two RPCs per getFileStatus (InfoBucket + getFileStatus), one
+ *       InfoBucket RPC per call ({@link #TOTAL_CALLS});</li>
+ *   <li>this branch (HDDS-15925): getFileStatus validates the bucket layout
+ *       server-side, so the redundant InfoBucket RPC is removed entirely (zero
+ *       for the workload).</li>
+ * </ul>
+ * The getFileStatus RPC count is identical either way, showing the 
optimization
+ * removes only the redundant InfoBucket RPC and changes no behaviour; the
+ * latency percentiles show the effect of dropping that RPC per call.
+ *
+ * <p>A second test runs the same workload single-threaded and then across
+ * {@link #CONCURRENT_THREADS} client threads sharing one {@code FileSystem}, 
and
+ * logs the two side by side, confirming the InfoBucket RPC removal holds under
+ * concurrency while throughput scales with the added client threads.
+ */
+@Tag("benchmark")
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+public class TestOfsGetFileStatusBenchmark {
+
+  private static final Logger LOG =
+      LoggerFactory.getLogger(TestOfsGetFileStatusBenchmark.class);
+
+  /** Distinct buckets in the working set. */
+  private static final int NUM_BUCKETS = 200;
+  /** getFileStatus calls per bucket. */
+  private static final int ACCESSES_PER_BUCKET = 10;
+  private static final int TOTAL_CALLS = NUM_BUCKETS * ACCESSES_PER_BUCKET;
+  private static final long SHUFFLE_SEED = 20250831L;
+  /** Client threads issuing getFileStatus in the concurrent comparison. */
+  private static final int CONCURRENT_THREADS = 10;
+
+  private MiniOzoneCluster cluster;
+  private OzoneClient client;
+  private OzoneConfiguration conf;
+  private String rootPath;
+
+  private final List<Path> accessSequence = new ArrayList<>(TOTAL_CALLS);
+
+  @BeforeAll
+  void init() throws IOException, InterruptedException, TimeoutException {
+    conf = new OzoneConfiguration();
+    conf.set(OMConfigKeys.OZONE_DEFAULT_BUCKET_LAYOUT,
+        BucketLayout.FILE_SYSTEM_OPTIMIZED.name());
+    cluster = MiniOzoneCluster.newBuilder(conf)
+        .setNumDatanodes(3)
+        .build();
+    cluster.waitForClusterToBeReady();
+    client = cluster.newClient();
+    rootPath = String.format("%s://%s/",
+        OzoneConsts.OZONE_OFS_URI_SCHEME, conf.get(OZONE_OM_ADDRESS_KEY));
+
+    // One volume, NUM_BUCKETS FSO buckets, one file per bucket. The access
+    // sequence lists each file ACCESSES_PER_BUCKET times, then is shuffled 
with
+    // a fixed seed so hits and misses interleave the way a real workload 
would.
+    ObjectStore objectStore = client.getObjectStore();
+    String volName = "benchvol";
+    objectStore.createVolume(volName);
+    OzoneVolume volume = objectStore.getVolume(volName);
+    BucketArgs fsoArgs = BucketArgs.newBuilder()
+        .setBucketLayout(BucketLayout.FILE_SYSTEM_OPTIMIZED).build();
+    try (FileSystem setupFs =
+        FileSystem.newInstance(URI.create(rootPath), conf)) {
+      for (int i = 0; i < NUM_BUCKETS; i++) {
+        String buckName = "benchbucket-" + i;
+        volume.createBucket(buckName, fsoArgs);
+        Path file = new Path("/" + volName + "/" + buckName + "/file");
+        ContractTestUtils.touch(setupFs, file);
+        for (int a = 0; a < ACCESSES_PER_BUCKET; a++) {
+          accessSequence.add(file);
+        }
+      }
+    }
+    Collections.shuffle(accessSequence, new Random(SHUFFLE_SEED));
+  }
+
+  @AfterAll
+  void shutdown() {
+    IOUtils.closeQuietly(client);
+    if (cluster != null) {
+      cluster.shutdown();
+    }
+  }
+
+  @Test
+  void benchmarkGetFileStatusWorkflow() throws Exception {
+    // Prime OM-side state and the JVM so the measured run pays no cold-start.
+    runWorkload(1);
+
+    Result measured = runWorkload(1);
+
+    assertWorkloadInvariants(measured);
+    logResult("single-threaded", measured);
+  }
+
+  @Test
+  void benchmarkConcurrentVsSingleThreaded() throws Exception {
+    // Warm the JVM/OM, then measure the same workload single-threaded and 
again
+    // across CONCURRENT_THREADS client threads sharing one FileSystem. The
+    // InfoBucket RPC removal holds under concurrency, so the getFileStatus RPC
+    // count and InfoBucket RPC count match the single-threaded run.
+    runWorkload(1);
+
+    Result single = runWorkload(1);
+    Result concurrent = runWorkload(CONCURRENT_THREADS);
+
+    assertWorkloadInvariants(single);
+    assertWorkloadInvariants(concurrent);
+
+    logResult("single-threaded", single);
+    logResult(CONCURRENT_THREADS + " concurrent threads", concurrent);
+    LOG.info(String.format("%n"
+            + "single-threaded vs %d concurrent threads (optimized)%n"
+            + "  InfoBucket RPCs     : %d  ->  %d%n"
+            + "  getFileStatus RPCs  : %d  ->  %d%n"
+            + "  latency mean (ms)   : %.3f  ->  %.3f%n"
+            + "  latency p99 (ms)    : %.3f  ->  %.3f%n"
+            + "  throughput (ops/s)  : %.1f  ->  %.1f  (%.2fx)%n",
+        CONCURRENT_THREADS,
+        single.infoBucketRpcs, concurrent.infoBucketRpcs,
+        single.getFileStatusRpcs, concurrent.getFileStatusRpcs,
+        millis(single.mean()), millis(concurrent.mean()),
+        millis(single.percentile(0.99)), millis(concurrent.percentile(0.99)),
+        single.opsPerSecond(), concurrent.opsPerSecond(),
+        concurrent.opsPerSecond() / single.opsPerSecond()));
+  }
+
+  /**
+   * Invariants that hold on both the baseline and this branch, so the 
identical
+   * benchmark passes in either worktree: every getFileStatus still issues its
+   * getFileStatus RPC, and the InfoBucket RPCs lie between the best case 
(zero —
+   * the server-side path removes the RPC) and the baseline worst case (one per
+   * call).
+   */
+  private void assertWorkloadInvariants(Result r) {
+    assertThat(r.getFileStatusRpcs).isEqualTo(TOTAL_CALLS);
+    assertThat(r.infoBucketRpcs)
+        .isBetween(0L, (long) TOTAL_CALLS);
+  }
+
+  private void logResult(String label, Result r) {
+    double infoBucketEliminated =
+        (TOTAL_CALLS - r.infoBucketRpcs) / (double) TOTAL_CALLS;
+    LOG.info(String.format("%n"
+            + "OFS getFileStatus RPC benchmark (HDDS-15925) — %s%n"
+            + "  buckets=%d, accesses/bucket=%d, total getFileStatus=%d, "
+            + "threads=%d%n"
+            + "  
------------------------------------------------------------%n"
+            + "  InfoBucket RPCs             : %d%n"
+            + "  getFileStatus RPCs          : %d%n"
+            + "  InfoBucket RPCs per call    : %.2f%n"
+            + "  InfoBucket RPCs eliminated  : %.0f%%%n"
+            + "  
------------------------------------------------------------%n"
+            + "  latency mean                : %.3f ms%n"
+            + "  latency p50                 : %.3f ms%n"
+            + "  latency p90                 : %.3f ms%n"
+            + "  latency p99                 : %.3f ms%n"
+            + "  latency max                 : %.3f ms%n"
+            + "  throughput                  : %.1f getFileStatus/s%n",
+        label, NUM_BUCKETS, ACCESSES_PER_BUCKET, TOTAL_CALLS, r.threads,
+        r.infoBucketRpcs, r.getFileStatusRpcs,
+        r.infoBucketRpcs / (double) TOTAL_CALLS,
+        infoBucketEliminated * 100,
+        millis(r.mean()), millis(r.percentile(0.50)),
+        millis(r.percentile(0.90)), millis(r.percentile(0.99)),
+        millis(r.max()), r.opsPerSecond()));
+  }
+
+  private static double millis(double nanos) {
+    return nanos / (double) TimeUnit.MILLISECONDS.toNanos(1);
+  }
+
+  private Result runWorkload(int threads)
+      throws IOException, InterruptedException {
+    OzoneConfiguration runConf = new OzoneConfiguration(conf);
+    runConf.set(FS_DEFAULT_NAME_KEY, rootPath);
+
+    OMMetrics metrics = cluster.getOzoneManager().getMetrics();
+    long[] latencyNanos = new long[TOTAL_CALLS];
+    try (FileSystem fs = FileSystem.newInstance(URI.create(rootPath), 
runConf)) {
+      long bucketInfosBefore = metrics.getNumBucketInfos();
+      long getFileStatusBefore = metrics.getNumGetFileStatus();
+      long elapsedNanos = threads == 1
+          ? runSequential(fs, latencyNanos)
+          : runConcurrent(fs, threads, latencyNanos);
+      return new Result(threads,
+          metrics.getNumBucketInfos() - bucketInfosBefore,
+          metrics.getNumGetFileStatus() - getFileStatusBefore,
+          elapsedNanos, latencyNanos);
+    }
+  }
+
+  private long runSequential(FileSystem fs, long[] latencyNanos)
+      throws IOException {
+    long startNanos = System.nanoTime();
+    int i = 0;
+    for (Path path : accessSequence) {
+      long callStart = System.nanoTime();
+      fs.getFileStatus(path);
+      latencyNanos[i++] = System.nanoTime() - callStart;
+    }
+    return System.nanoTime() - startNanos;
+  }
+
+  private long runConcurrent(FileSystem fs, int threads, long[] latencyNanos)
+      throws IOException, InterruptedException {
+    ExecutorService pool = Executors.newFixedThreadPool(threads);
+    CountDownLatch startGate = new CountDownLatch(1);
+    CountDownLatch doneGate = new CountDownLatch(threads);
+    AtomicReference<IOException> failure = new AtomicReference<>();
+    // Disjoint, contiguous slices of the shuffled sequence; each thread writes
+    // only its own latency indices, so no synchronization is needed per call.
+    int chunk = (TOTAL_CALLS + threads - 1) / threads;
+    for (int t = 0; t < threads; t++) {
+      final int from = t * chunk;
+      final int to = Math.min(TOTAL_CALLS, from + chunk);
+      pool.submit(() -> {
+        try {
+          startGate.await();
+          for (int i = from; i < to; i++) {
+            long callStart = System.nanoTime();
+            fs.getFileStatus(accessSequence.get(i));
+            latencyNanos[i] = System.nanoTime() - callStart;
+          }
+        } catch (IOException e) {
+          failure.compareAndSet(null, e);
+        } catch (InterruptedException e) {
+          Thread.currentThread().interrupt();
+        } finally {
+          doneGate.countDown();
+        }
+      });
+    }
+    long startNanos = System.nanoTime();
+    startGate.countDown();
+    doneGate.await();
+    long elapsedNanos = System.nanoTime() - startNanos;
+    pool.shutdownNow();
+    if (failure.get() != null) {
+      throw failure.get();
+    }
+    return elapsedNanos;
+  }
+
+  private static final class Result {
+    private final int threads;
+    private final long infoBucketRpcs;
+    private final long getFileStatusRpcs;
+    private final long elapsedNanos;
+    private final long[] sortedLatencyNanos;
+
+    Result(int threads, long infoBucketRpcs, long getFileStatusRpcs,
+        long elapsedNanos, long[] latencyNanos) {
+      this.threads = threads;
+      this.infoBucketRpcs = infoBucketRpcs;
+      this.getFileStatusRpcs = getFileStatusRpcs;
+      this.elapsedNanos = elapsedNanos;
+      this.sortedLatencyNanos = latencyNanos.clone();
+      Arrays.sort(this.sortedLatencyNanos);
+    }
+
+    double opsPerSecond() {
+      return TOTAL_CALLS
+          / (elapsedNanos / (double) TimeUnit.SECONDS.toNanos(1));
+    }
+
+    double mean() {
+      long sum = 0;
+      for (long l : sortedLatencyNanos) {
+        sum += l;
+      }
+      return sum / (double) sortedLatencyNanos.length;
+    }
+
+    double percentile(double q) {
+      int idx = (int) Math.ceil(q * sortedLatencyNanos.length) - 1;
+      idx = Math.max(0, Math.min(sortedLatencyNanos.length - 1, idx));
+      return sortedLatencyNanos[idx];
+    }
+
+    double max() {
+      return sortedLatencyNanos[sortedLatencyNanos.length - 1];
+    }
+  }
+}
diff --git 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOmAcls.java
 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOmAcls.java
index 90d6b128e74..30190c75d5b 100644
--- 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOmAcls.java
+++ 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOmAcls.java
@@ -46,6 +46,7 @@
 import org.apache.hadoop.ozone.client.OzoneClient;
 import org.apache.hadoop.ozone.om.exceptions.OMException;
 import org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes;
+import org.apache.hadoop.ozone.om.helpers.BucketLayout;
 import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer;
 import org.apache.hadoop.ozone.security.acl.IOzoneObj;
 import org.apache.hadoop.ozone.security.acl.OzoneObj;
@@ -193,7 +194,8 @@ public void testReadKeyPermissionDenied() throws Exception {
 
   @Test
   public void testGetFileStatusPermissionDenied() throws Exception {
-    OzoneBucket bucket = DataTestUtil.createVolumeAndBucket(client);
+    OzoneBucket bucket = DataTestUtil.createVolumeAndBucket(client,
+        BucketLayout.FILE_SYSTEM_OPTIMIZED);
     DataTestUtil.createKey(bucket, "testKey", 
"testcontent".getBytes(StandardCharsets.UTF_8));
 
     authorizer.keyAclAllow = false;
diff --git 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/snapshot/OmSnapshotTests.java
 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/snapshot/OmSnapshotTests.java
index fe8f4b24e7e..516a87386a8 100644
--- 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/snapshot/OmSnapshotTests.java
+++ 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/snapshot/OmSnapshotTests.java
@@ -65,6 +65,7 @@
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
+import static org.junit.jupiter.api.Assumptions.assumeFalse;
 import static org.junit.jupiter.api.Assumptions.assumeTrue;
 
 import com.google.common.collect.Lists;
@@ -527,6 +528,7 @@ public void checkKey() throws Exception {
     KeyInfoWithVolumeContext fileInfo = writeClient.getKeyInfo(keyArgs, false);
     assertEquals(fileInfo.getKeyInfo().getKeyName(), snapshotKeyPrefix + key1);
 
+    assumeFalse(bucketLayout.equals(BucketLayout.OBJECT_STORE));
     OzoneFileStatus ozoneFileStatus = writeClient.getFileStatus(keyArgs);
     assertEquals(ozoneFileStatus.getKeyInfo().getKeyName(),
         snapshotKeyPrefix + key1);
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmMetadataReader.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmMetadataReader.java
index 434d05132bf..d6db1cb35d8 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmMetadataReader.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmMetadataReader.java
@@ -53,6 +53,7 @@
 import org.apache.hadoop.ozone.om.helpers.OmBucketInfo;
 import org.apache.hadoop.ozone.om.helpers.OmKeyArgs;
 import org.apache.hadoop.ozone.om.helpers.OmKeyInfo;
+import org.apache.hadoop.ozone.om.helpers.OzoneFSUtils;
 import org.apache.hadoop.ozone.om.helpers.OzoneFileStatus;
 import org.apache.hadoop.ozone.om.helpers.OzoneFileStatusLight;
 import org.apache.hadoop.ozone.om.helpers.S3VolumeContext;
@@ -307,6 +308,18 @@ public OzoneFileStatus getFileStatus(OmKeyArgs args) 
throws IOException {
     args = bucket.update(args);
 
     try {
+      if (bucket.bucketLayout() != null) {
+        try {
+          OzoneFSUtils.validateBucketLayout(bucket.requestedBucket(),
+              bucket.bucketLayout());
+        } catch (IllegalArgumentException e) {
+          // Convert to an OMException so it is returned to the client as a
+          // normal (non-retryable) RPC response instead of escaping the read
+          // handler's IOException catch and triggering a client retry storm.
+          throw new OMException(e.getMessage(),
+              ResultCodes.NOT_SUPPORTED_OPERATION);
+        }
+      }
       if (isAclEnabled) {
         checkAcls(getResourceType(args), StoreType.OZONE, ACLType.READ, 
bucket, args.getKeyName());
       }
diff --git 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestOMMetadataReader.java
 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestOMMetadataReader.java
index bb5d7fb289d..547ce05e09b 100644
--- 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestOMMetadataReader.java
+++ 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestOMMetadataReader.java
@@ -26,9 +26,11 @@
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -47,6 +49,7 @@
 import org.apache.hadoop.ozone.audit.AuditLogger;
 import org.apache.hadoop.ozone.om.exceptions.OMException;
 import org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes;
+import org.apache.hadoop.ozone.om.helpers.BucketLayout;
 import org.apache.hadoop.ozone.om.helpers.ListKeysResult;
 import org.apache.hadoop.ozone.om.helpers.OmKeyArgs;
 import org.apache.hadoop.ozone.om.helpers.OzoneFileStatus;
@@ -703,4 +706,64 @@ private RequestContext getContext() {
       return context;
     }
   }
+
+  @Test
+  public void getFileStatusRejectsObjectStoreLayout() throws Exception {
+    OzoneManager ozoneManager = mock(OzoneManager.class);
+    KeyManager keyManager = mock(KeyManager.class);
+    when(ozoneManager.getAclsEnabled()).thenReturn(false);
+    
when(ozoneManager.getBucketManager()).thenReturn(mock(BucketManager.class));
+    
when(ozoneManager.getVolumeManager()).thenReturn(mock(VolumeManager.class));
+    
when(ozoneManager.getPerfMetrics()).thenReturn(mock(OMPerformanceMetrics.class));
+    when(ozoneManager.resolveBucketLink(any(OmKeyArgs.class)))
+        .thenReturn(new ResolvedBucket("vol", "obs-bucket", "vol", 
"obs-bucket",
+            "owner", BucketLayout.OBJECT_STORE));
+
+    OmMetadataReader reader = new OmMetadataReader(keyManager,
+        mock(PrefixManager.class), ozoneManager, mock(org.slf4j.Logger.class),
+        mock(AuditLogger.class), mock(OmMetadataReaderMetrics.class), null);
+
+    OmKeyArgs keyArgs = new OmKeyArgs.Builder()
+        .setVolumeName("vol")
+        .setBucketName("obs-bucket")
+        .setKeyName("key1")
+        .build();
+
+    OMException exception = assertThrows(OMException.class,
+        () -> reader.getFileStatus(keyArgs));
+    assertEquals(ResultCodes.NOT_SUPPORTED_OPERATION, exception.getResult());
+    assertTrue(exception.getMessage().contains("obs-bucket"));
+    assertTrue(exception.getMessage().contains("OBJECT_STORE"));
+    verify(keyManager, never()).getFileStatus(any(), anyString());
+  }
+
+  @Test
+  public void getFileStatusAllowsLegacyLayout() throws Exception {
+    OzoneManager ozoneManager = mock(OzoneManager.class);
+    KeyManager keyManager = mock(KeyManager.class);
+    when(ozoneManager.getAclsEnabled()).thenReturn(false);
+    
when(ozoneManager.getBucketManager()).thenReturn(mock(BucketManager.class));
+    
when(ozoneManager.getVolumeManager()).thenReturn(mock(VolumeManager.class));
+    
when(ozoneManager.getPerfMetrics()).thenReturn(mock(OMPerformanceMetrics.class));
+    when(ozoneManager.resolveBucketLink(any(OmKeyArgs.class)))
+        .thenReturn(new ResolvedBucket("vol", "legacy-bucket", "vol",
+            "legacy-bucket", "owner", BucketLayout.LEGACY));
+    OzoneFileStatus expectedStatus = new OzoneFileStatus();
+    when(keyManager.getFileStatus(any(OmKeyArgs.class), anyString()))
+        .thenReturn(expectedStatus);
+
+    OmMetadataReader reader = new OmMetadataReader(keyManager,
+        mock(PrefixManager.class), ozoneManager, mock(org.slf4j.Logger.class),
+        mock(AuditLogger.class), mock(OmMetadataReaderMetrics.class), null);
+
+    OmKeyArgs keyArgs = new OmKeyArgs.Builder()
+        .setVolumeName("vol")
+        .setBucketName("legacy-bucket")
+        .setKeyName("key1")
+        .build();
+
+    OzoneFileStatus status = reader.getFileStatus(keyArgs);
+    assertEquals(expectedStatus, status);
+    verify(keyManager).getFileStatus(any(OmKeyArgs.class), anyString());
+  }
 }
diff --git 
a/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/BasicRootedOzoneClientAdapterImpl.java
 
b/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/BasicRootedOzoneClientAdapterImpl.java
index 261f3a83761..9b1ed021267 100644
--- 
a/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/BasicRootedOzoneClientAdapterImpl.java
+++ 
b/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/BasicRootedOzoneClientAdapterImpl.java
@@ -76,6 +76,7 @@
 import org.apache.hadoop.ozone.OmUtils;
 import org.apache.hadoop.ozone.OzoneConfigKeys;
 import org.apache.hadoop.ozone.OzoneFsServerDefaults;
+import org.apache.hadoop.ozone.OzoneManagerVersion;
 import org.apache.hadoop.ozone.client.BucketArgs;
 import org.apache.hadoop.ozone.client.ObjectStore;
 import org.apache.hadoop.ozone.client.OzoneBucket;
@@ -345,6 +346,12 @@ private OzoneBucket getBucket(String volumeStr, String 
bucketStr,
         }
         // Try get bucket again
         bucket = proxy.getBucketDetails(volumeStr, bucketStr);
+
+        BucketLayout resolvedBucketLayout =
+            OzoneClientUtils.resolveLinkBucketLayout(bucket, objectStore,
+                new HashSet<>());
+
+        OzoneFSUtils.validateBucketLayout(bucket.getName(), 
resolvedBucketLayout);
       } else {
         throw ex;
       }
@@ -690,19 +697,40 @@ public FileStatusAdapter getFileStatus(String path, URI 
uri,
    * Return FileStatusAdapter based on OFSPath being a
    * valid bucket path or valid snapshot path.
    * Throws exception in case of failure.
+   *
+   * <p>Non-snapshot paths call OM GetFileStatus directly (HDDS-15925) without 
a
+   * prior InfoBucket RPC. OBJECT_STORE buckets are rejected by OM 
GetFileStatus.
+   * Mutating OFS operations still validate layout via {@link 
#getBucket(OFSPath, boolean)}.
+   *
+   * <p>The direct call is only safe once the OM performs the server-side
+   * OBJECT_STORE rejection. During a rolling upgrade a new client can talk to 
an
+   * older OM that lacks that check, so when the negotiated OM version predates
+   * {@link OzoneManagerVersion#GET_FILE_STATUS_REJECTS_OBS} we fall back to 
the
+   * pre-HDDS-15925 path that fetches the bucket and validates its layout
+   * client-side.
    */
   private FileStatusAdapter getFileStatusForKeyOrSnapshot(
       OFSPath ofsPath, URI uri, Path qualifiedPath, String userName,
       boolean headOp) throws IOException {
     String key = ofsPath.getKeyName();
     try {
-      OzoneBucket bucket = getBucket(ofsPath, false);
       if (ofsPath.isSnapshotPath()) {
+        OzoneBucket bucket = getBucket(ofsPath, false);
         OzoneVolume volume = objectStore.getVolume(ofsPath.getVolumeName());
         return getFileStatusAdapterWithSnapshotIndicator(
             volume, bucket, uri);
       } else {
-        OzoneFileStatus status = bucket.getFileStatus(key, headOp);
+        OzoneFileStatus status;
+        if (proxy.getOmVersion()
+            .compareTo(OzoneManagerVersion.GET_FILE_STATUS_REJECTS_OBS) >= 0) {
+          status = proxy.getOzoneFileStatus(ofsPath.getVolumeName(),
+              ofsPath.getBucketName(), key, headOp);
+        } else {
+          // Older OM has no server-side OBJECT_STORE check; validate the 
bucket
+          // layout on the client, matching pre-HDDS-15925 behavior.
+          OzoneBucket bucket = getBucket(ofsPath, false);
+          status = bucket.getFileStatus(key, headOp);
+        }
         return toFileStatusAdapter(status, userName, uri, qualifiedPath,
             ofsPath.getNonKeyPath());
       }
@@ -711,6 +739,12 @@ private FileStatusAdapter getFileStatusForKeyOrSnapshot(
         throw new FileNotFoundException(key + ": No such file or directory!");
       } else if (e.getResult() == OMException.ResultCodes.BUCKET_NOT_FOUND) {
         throw new FileNotFoundException(key + ": Bucket doesn't exist!");
+      } else if (e.getResult()
+          == OMException.ResultCodes.NOT_SUPPORTED_OPERATION) {
+        // OM rejects getFileStatus on an OBJECT_STORE bucket (no file system
+        // semantics). Surface it as IllegalArgumentException, matching the
+        // pre-HDDS-15925 client-side layout check.
+        throw new IllegalArgumentException(e.getMessage());
       }
       throw e;
     }
@@ -748,6 +782,12 @@ public Collection<FileStatus> getTrashRoots(boolean 
allUsers,
         Iterator<? extends OzoneBucket> bucketIter = volume.listBuckets("");
         while (bucketIter.hasNext()) {
           OzoneBucket bucket = bucketIter.next();
+          // OBJECT_STORE buckets have no file system semantics, so no trash
+          // root. Skip them; probing would fail getFileStatus with
+          // IllegalArgumentException and abort the whole scan.
+          if (BucketLayout.OBJECT_STORE.equals(bucket.getBucketLayout())) {
+            continue;
+          }
           Path bucketPath = new Path(volumePath, bucket.getName());
           Path trashRoot = new Path(bucketPath, FileSystem.TRASH_PREFIX);
           if (allUsers) {
diff --git 
a/hadoop-ozone/ozonefs-common/src/test/java/org/apache/hadoop/fs/ozone/TestBasicRootedOzoneClientAdapterHeadOp.java
 
b/hadoop-ozone/ozonefs-common/src/test/java/org/apache/hadoop/fs/ozone/TestBasicRootedOzoneClientAdapterHeadOp.java
index 8f67adef134..25e067556d2 100644
--- 
a/hadoop-ozone/ozonefs-common/src/test/java/org/apache/hadoop/fs/ozone/TestBasicRootedOzoneClientAdapterHeadOp.java
+++ 
b/hadoop-ozone/ozonefs-common/src/test/java/org/apache/hadoop/fs/ozone/TestBasicRootedOzoneClientAdapterHeadOp.java
@@ -27,6 +27,7 @@
 import static org.mockito.Mockito.CALLS_REAL_METHODS;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -40,9 +41,11 @@
 import org.apache.hadoop.hdds.client.RatisReplicationConfig;
 import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
 import org.apache.hadoop.ozone.OFSPath;
+import org.apache.hadoop.ozone.OzoneManagerVersion;
 import org.apache.hadoop.ozone.client.ObjectStore;
 import org.apache.hadoop.ozone.client.OzoneBucket;
 import org.apache.hadoop.ozone.client.OzoneVolume;
+import org.apache.hadoop.ozone.client.protocol.ClientProtocol;
 import org.apache.hadoop.ozone.om.exceptions.OMException;
 import org.apache.hadoop.ozone.om.helpers.OmKeyInfo;
 import org.apache.hadoop.ozone.om.helpers.OzoneFileStatus;
@@ -62,15 +65,22 @@ public class TestBasicRootedOzoneClientAdapterHeadOp {
 
   private BasicRootedOzoneClientAdapterImpl adapter;
   private OzoneBucket bucket;
+  private ClientProtocol proxy;
 
   @BeforeEach
   public void setUp() throws Exception {
     adapter = mock(BasicRootedOzoneClientAdapterImpl.class, 
CALLS_REAL_METHODS);
     bucket = mock(OzoneBucket.class);
+    proxy = mock(ClientProtocol.class);
+    when(proxy.getOmVersion())
+        .thenReturn(OzoneManagerVersion.GET_FILE_STATUS_REJECTS_OBS);
     doReturn(bucket).when(adapter).getBucket(any(OFSPath.class), eq(false));
 
-    // Inject a mock object store so the volume/snapshot dispatch branches can
-    // run without a live OM connection.
+    Field proxyField =
+        BasicRootedOzoneClientAdapterImpl.class.getDeclaredField("proxy");
+    proxyField.setAccessible(true);
+    proxyField.set(adapter, proxy);
+
     OzoneVolume volume = mock(OzoneVolume.class);
     when(volume.getName()).thenReturn("vol");
     when(volume.getOwner()).thenReturn("user");
@@ -101,25 +111,45 @@ private static OzoneFileStatus fileStatus(boolean isDir) {
 
   @Test
   public void keyPathThreadsHeadOp() throws IOException {
-    when(bucket.getFileStatus(anyString(), anyBoolean()))
+    when(proxy.getOzoneFileStatus(anyString(), anyString(), anyString(), 
anyBoolean()))
         .thenReturn(fileStatus(false));
 
     assertFalse(adapter.getFileStatus("/vol/bucket/key", URI_OFS, WORKING_DIR,
         "user", true).isDir());
 
     ArgumentCaptor<Boolean> headOp = ArgumentCaptor.forClass(Boolean.class);
-    verify(bucket).getFileStatus(anyString(), headOp.capture());
+    verify(proxy).getOzoneFileStatus(eq("vol"), eq("bucket"), eq("key"),
+        headOp.capture());
     assertTrue(headOp.getValue());
   }
 
   @Test
   public void fourArgOverloadDoesNotUseHeadOp() throws IOException {
-    when(bucket.getFileStatus(anyString(), anyBoolean()))
+    when(proxy.getOzoneFileStatus(anyString(), anyString(), anyString(), 
anyBoolean()))
         .thenReturn(fileStatus(true));
 
     assertTrue(adapter.getFileStatus("/vol/bucket/key", URI_OFS, WORKING_DIR,
         "user").isDir());
-    verify(bucket).getFileStatus(anyString(), eq(false));
+    verify(proxy).getOzoneFileStatus(eq("vol"), eq("bucket"), eq("key"), 
eq(false));
+  }
+
+  @Test
+  public void olderOmFallsBackToClientSideBucketCheck() throws IOException {
+    // An OM older than GET_FILE_STATUS_REJECTS_OBS has no server-side
+    // OBJECT_STORE rejection, so the adapter must use the pre-HDDS-15925 path:
+    // fetch the bucket (which validates layout client-side) and call
+    // OzoneBucket#getFileStatus, without issuing a direct OM GetFileStatus.
+    when(proxy.getOmVersion())
+        .thenReturn(OzoneManagerVersion.S3_BUCKET_TAGGING_API);
+    when(bucket.getFileStatus(anyString(), anyBoolean()))
+        .thenReturn(fileStatus(false));
+
+    assertFalse(adapter.getFileStatus("/vol/bucket/key", URI_OFS, WORKING_DIR,
+        "user", true).isDir());
+
+    verify(bucket).getFileStatus(eq("key"), eq(true));
+    verify(proxy, never())
+        .getOzoneFileStatus(anyString(), anyString(), anyString(), 
anyBoolean());
   }
 
   @Test
@@ -130,7 +160,7 @@ public void rootPathReturnsDirectory() throws IOException {
 
   @Test
   public void fileNotFoundMappedToFileNotFoundException() throws IOException {
-    when(bucket.getFileStatus(anyString(), anyBoolean()))
+    when(proxy.getOzoneFileStatus(anyString(), anyString(), anyString(), 
anyBoolean()))
         .thenThrow(new OMException("missing",
             OMException.ResultCodes.FILE_NOT_FOUND));
     assertThrows(FileNotFoundException.class,
@@ -140,7 +170,7 @@ public void fileNotFoundMappedToFileNotFoundException() 
throws IOException {
 
   @Test
   public void otherOMExceptionPropagates() throws IOException {
-    when(bucket.getFileStatus(anyString(), anyBoolean()))
+    when(proxy.getOzoneFileStatus(anyString(), anyString(), anyString(), 
anyBoolean()))
         .thenThrow(new OMException("boom",
             OMException.ResultCodes.INTERNAL_ERROR));
     assertThrows(OMException.class,
@@ -150,7 +180,7 @@ public void otherOMExceptionPropagates() throws IOException 
{
 
   @Test
   public void bucketNotFoundMappedToFileNotFoundException() throws IOException 
{
-    when(bucket.getFileStatus(anyString(), anyBoolean()))
+    when(proxy.getOzoneFileStatus(anyString(), anyString(), anyString(), 
anyBoolean()))
         .thenThrow(new OMException("no bucket",
             OMException.ResultCodes.BUCKET_NOT_FOUND));
     assertThrows(FileNotFoundException.class,
@@ -169,7 +199,6 @@ public void snapshotIndicatorPathReturnsDirectory() throws 
IOException {
     when(bucket.getVolumeName()).thenReturn("vol");
     when(bucket.getName()).thenReturn("bucket");
     when(bucket.getCreationTime()).thenReturn(Instant.EPOCH);
-    // keyName == ".snapshot" is the snapshot indicator path.
     assertTrue(adapter.getFileStatus("/vol/bucket/.snapshot", URI_OFS,
         WORKING_DIR, "user", true).isDir());
   }
diff --git 
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/client/ClientProtocolStub.java
 
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/client/ClientProtocolStub.java
index 40e384d2c54..816922f5dac 100644
--- 
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/client/ClientProtocolStub.java
+++ 
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/client/ClientProtocolStub.java
@@ -34,6 +34,7 @@
 import org.apache.hadoop.io.Text;
 import org.apache.hadoop.ozone.OzoneAcl;
 import org.apache.hadoop.ozone.OzoneFsServerDefaults;
+import org.apache.hadoop.ozone.OzoneManagerVersion;
 import org.apache.hadoop.ozone.client.io.OzoneDataStreamOutput;
 import org.apache.hadoop.ozone.client.io.OzoneInputStream;
 import org.apache.hadoop.ozone.client.io.OzoneOutputStream;
@@ -617,6 +618,11 @@ public TenantStateList listTenant() throws IOException {
     return null;
   }
 
+  @Override
+  public OzoneManagerVersion getOmVersion() {
+    return OzoneManagerVersion.CURRENT;
+  }
+
   @Override
   public OzoneFsServerDefaults getServerDefaults() throws IOException {
     return null;


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to