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

adoroszlai 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 bc89991f35f HDDS-15004. Stabilize 
TestReconContainerEndpoint#testContainerEndpointForOBSBucket (#10116)
bc89991f35f is described below

commit bc89991f35f938bf8ec24d56248d26f0d0051c60
Author: Arun Sarin <[email protected]>
AuthorDate: Thu May 7 15:55:07 2026 +0530

    HDDS-15004. Stabilize 
TestReconContainerEndpoint#testContainerEndpointForOBSBucket (#10116)
---
 .../ozone/recon/TestReconContainerEndpoint.java    | 69 ++++++++++++++++++----
 .../ozone/recon/TestReconOmMetaManagerUtils.java   | 33 +++++++++++
 2 files changed, 90 insertions(+), 12 deletions(-)

diff --git 
a/hadoop-ozone/integration-test-recon/src/test/java/org/apache/hadoop/ozone/recon/TestReconContainerEndpoint.java
 
b/hadoop-ozone/integration-test-recon/src/test/java/org/apache/hadoop/ozone/recon/TestReconContainerEndpoint.java
index a8863046f6e..3ff0a636d81 100644
--- 
a/hadoop-ozone/integration-test-recon/src/test/java/org/apache/hadoop/ozone/recon/TestReconContainerEndpoint.java
+++ 
b/hadoop-ozone/integration-test-recon/src/test/java/org/apache/hadoop/ozone/recon/TestReconContainerEndpoint.java
@@ -23,10 +23,13 @@
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.concurrent.CompletableFuture;
 import javax.ws.rs.core.Response;
 import org.apache.hadoop.hdds.conf.OzoneConfiguration;
 import org.apache.hadoop.hdds.scm.server.OzoneStorageContainerManager;
+import org.apache.hadoop.hdds.utils.IOUtils;
 import org.apache.hadoop.ozone.MiniOzoneCluster;
 import org.apache.hadoop.ozone.client.BucketArgs;
 import org.apache.hadoop.ozone.client.ObjectStore;
@@ -36,11 +39,15 @@
 import org.apache.hadoop.ozone.om.OMConfigKeys;
 import org.apache.hadoop.ozone.om.helpers.BucketLayout;
 import org.apache.hadoop.ozone.om.helpers.OmBucketInfo;
+import org.apache.hadoop.ozone.om.helpers.OmKeyArgs;
+import org.apache.hadoop.ozone.om.helpers.OmKeyLocationInfo;
 import org.apache.hadoop.ozone.recon.api.ContainerEndpoint;
 import org.apache.hadoop.ozone.recon.api.types.KeyMetadata;
 import org.apache.hadoop.ozone.recon.api.types.KeysResponse;
 import org.apache.hadoop.ozone.recon.recovery.ReconOMMetadataManager;
+import org.apache.hadoop.ozone.recon.spi.ReconContainerMetadataManager;
 import org.apache.hadoop.ozone.recon.spi.impl.OzoneManagerServiceProviderImpl;
+import org.apache.hadoop.ozone.recon.tasks.ContainerKeyMapperHelper;
 import org.apache.hadoop.ozone.recon.tasks.ReconTaskControllerImpl;
 import org.apache.ozone.test.GenericTestUtils;
 import org.junit.jupiter.api.AfterEach;
@@ -60,6 +67,9 @@ public class TestReconContainerEndpoint {
 
   @BeforeEach
   public void init() throws Exception {
+    // ContainerKeyMapper tasks share static maps/flags across the JVM; reset 
so a
+    // prior test method cannot break mapper state for this cluster instance.
+    ContainerKeyMapperHelper.clearSharedContainerCountMap();
     OzoneConfiguration conf = new OzoneConfiguration();
     conf.set(OMConfigKeys.OZONE_DEFAULT_BUCKET_LAYOUT,
         OMConfigKeys.OZONE_BUCKET_LAYOUT_FILE_SYSTEM_OPTIMIZED);
@@ -76,13 +86,9 @@ public void init() throws Exception {
   }
 
   @AfterEach
-  public void shutdown() throws IOException {
-    if (client != null) {
-      client.close();
-    }
-    if (cluster != null) {
-      cluster.shutdown();
-    }
+  public void shutdown() {
+    IOUtils.closeQuietly(client, cluster);
+    ContainerKeyMapperHelper.clearSharedContainerCountMap();
   }
 
   @Test
@@ -117,6 +123,9 @@ public void testContainerEndpointForFSOLayout() throws 
Exception {
     CompletableFuture<Void> completableFuture =
         
omMetaManagerUtils.waitForEventBufferEmpty(reconTaskController.getEventBuffer());
     GenericTestUtils.waitFor(completableFuture::isDone, 100, 30000);
+    completableFuture.join();
+    waitUntilReconIndexesKeysForPaths(volName, bucketName,
+        nestedDirKey, singleFileKey);
 
     //Search for the bucket from the bucket table and verify its FSO
     OmBucketInfo bucketInfo = cluster.getOzoneManager().getBucketInfo(volName, 
bucketName);
@@ -124,8 +133,7 @@ public void testContainerEndpointForFSOLayout() throws 
Exception {
     assertEquals(BucketLayout.FILE_SYSTEM_OPTIMIZED,
         bucketInfo.getBucketLayout());
 
-    // Assuming a known container ID that these keys have been written into
-    long testContainerID = 1L;
+    long testContainerID = getContainerIdForKey(volName, bucketName, 
nestedDirKey);
 
     // Query the ContainerEndpoint for the keys in the specified container
     Response response = getContainerEndpointResponse(testContainerID);
@@ -145,7 +153,7 @@ public void testContainerEndpointForFSOLayout() throws 
Exception {
     assertEquals("file1", keyMetadata.getKey());
     assertEquals("testvol/fsobucket/dir1/dir2/dir3/file1", 
keyMetadata.getCompletePath());
 
-    testContainerID = 2L;
+    testContainerID = getContainerIdForKey(volName, bucketName, singleFileKey);
     response = getContainerEndpointResponse(testContainerID);
     data = (KeysResponse) response.getEntity();
     keyMetadataList = data.getKeys();
@@ -186,14 +194,17 @@ public void testContainerEndpointForOBSBucket() throws 
Exception {
     CompletableFuture<Void> completableFuture =
         
omMetaManagerUtils.waitForEventBufferEmpty(reconTaskController.getEventBuffer());
     GenericTestUtils.waitFor(completableFuture::isDone, 100, 30000);
+    completableFuture.join();
+    waitUntilReconIndexesKeysForPaths(volumeName, obsBucketName, 
obsSingleFileKey);
 
     // Search for the bucket from the bucket table and verify its OBS
     OmBucketInfo bucketInfo = 
cluster.getOzoneManager().getBucketInfo(volumeName, obsBucketName);
     assertNotNull(bucketInfo);
     assertEquals(BucketLayout.OBJECT_STORE, bucketInfo.getBucketLayout());
 
-    // Initialize the ContainerEndpoint
-    long containerId = 1L;
+    long containerId = getContainerIdForKey(volumeName, obsBucketName,
+        obsSingleFileKey);
+
     Response response = getContainerEndpointResponse(containerId);
 
     assertNotNull(response, "Response should not be null.");
@@ -226,6 +237,40 @@ private Response getContainerEndpointResponse(long 
containerId) {
     return containerEndpoint.getKeysForContainer(containerId, 10, "");
   }
 
+  /**
+   * Wait until Recon's container-key index reflects all written keys (by 
container id).
+   * The OM event queue can be empty while a batch is still being processed.
+   */
+  private void waitUntilReconIndexesKeysForPaths(String volumeName,
+      String bucketName, String... keyPaths)
+      throws Exception {
+    Map<Long, Integer> requiredCountByContainer = new HashMap<>();
+    for (String keyPath : keyPaths) {
+      long containerId =
+          getContainerIdForKey(volumeName, bucketName, keyPath);
+      requiredCountByContainer.merge(containerId, 1, Integer::sum);
+    }
+    ReconContainerMetadataManager mgr =
+        recon.getReconServer().getReconContainerMetadataManager();
+    TestReconOmMetaManagerUtils.waitUntilReconKeyCounts(mgr, 
requiredCountByContainer);
+  }
+
+  private long getContainerIdForKey(String volumeName, String bucketName,
+      String keyName) throws IOException {
+    OmKeyArgs keyArgs = new OmKeyArgs.Builder()
+        .setVolumeName(volumeName)
+        .setBucketName(bucketName)
+        .setKeyName(keyName)
+        .build();
+    OmKeyLocationInfo location = cluster.getOzoneManager()
+        .lookupKey(keyArgs)
+        .getKeyLocationVersions()
+        .get(0)
+        .getBlocksLatestVersionOnly()
+        .get(0);
+    return location.getContainerID();
+  }
+
   private void writeTestData(String volumeName, String bucketName,
                              String keyPath, String data) throws Exception {
     try (OzoneOutputStream out = client.getObjectStore().getVolume(volumeName)
diff --git 
a/hadoop-ozone/integration-test-recon/src/test/java/org/apache/hadoop/ozone/recon/TestReconOmMetaManagerUtils.java
 
b/hadoop-ozone/integration-test-recon/src/test/java/org/apache/hadoop/ozone/recon/TestReconOmMetaManagerUtils.java
index 4ef84f2e6d9..8aa32ac40ba 100644
--- 
a/hadoop-ozone/integration-test-recon/src/test/java/org/apache/hadoop/ozone/recon/TestReconOmMetaManagerUtils.java
+++ 
b/hadoop-ozone/integration-test-recon/src/test/java/org/apache/hadoop/ozone/recon/TestReconOmMetaManagerUtils.java
@@ -17,7 +17,10 @@
 
 package org.apache.hadoop.ozone.recon;
 
+import java.io.IOException;
+import java.util.Map;
 import java.util.concurrent.CompletableFuture;
+import org.apache.hadoop.ozone.recon.spi.ReconContainerMetadataManager;
 import org.apache.hadoop.ozone.recon.tasks.OMUpdateEventBuffer;
 import org.apache.ozone.test.GenericTestUtils;
 
@@ -43,4 +46,34 @@ public CompletableFuture<Void> 
waitForEventBufferEmpty(OMUpdateEventBuffer event
       }
     });
   }
+
+  /**
+   * Waits until Recon's container-key index reports at least the given number 
of keys
+   * per container id. Use after OM sync when the event buffer can be empty 
while a
+   * dequeued batch is still being processed.
+   * <p>
+   * IO failures from {@code mgr} reads (including temporary {@code 
RocksDatabaseException}
+   * while Recon applies updates) are treated as "not ready yet"; the wait 
repeats until the
+   * timeout if counts never converge.
+   *
+   * @param mgr                      Recon container metadata manager
+   * @param minimumCountPerContainer map of container ID to minimum inclusive 
key count
+   * @throws Exception               if the condition is not met within the 
timeout or on interrupt
+   */
+  public static void waitUntilReconKeyCounts(ReconContainerMetadataManager mgr,
+      Map<Long, Integer> minimumCountPerContainer) throws Exception {
+    GenericTestUtils.waitFor(() -> {
+      try {
+        for (Map.Entry<Long, Integer> e : minimumCountPerContainer.entrySet()) 
{
+          if (mgr.getKeyCountForContainer(e.getKey()) < e.getValue()) {
+            return false;
+          }
+        }
+        return true;
+      } catch (IOException ex) {
+        // Retry: concurrent Recon indexing can transiently expose a closed 
Rocks handle.
+        return false;
+      }
+    }, 1000, 90000);
+  }
 }


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

Reply via email to