Skip to content

Commit bc89991

Browse files
authored
HDDS-15004. Stabilize TestReconContainerEndpoint#testContainerEndpointForOBSBucket (#10116)
1 parent 0f811d4 commit bc89991

2 files changed

Lines changed: 90 additions & 12 deletions

File tree

hadoop-ozone/integration-test-recon/src/test/java/org/apache/hadoop/ozone/recon/TestReconContainerEndpoint.java

Lines changed: 57 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,13 @@
2323
import java.io.IOException;
2424
import java.nio.charset.StandardCharsets;
2525
import java.util.Collection;
26+
import java.util.HashMap;
27+
import java.util.Map;
2628
import java.util.concurrent.CompletableFuture;
2729
import javax.ws.rs.core.Response;
2830
import org.apache.hadoop.hdds.conf.OzoneConfiguration;
2931
import org.apache.hadoop.hdds.scm.server.OzoneStorageContainerManager;
32+
import org.apache.hadoop.hdds.utils.IOUtils;
3033
import org.apache.hadoop.ozone.MiniOzoneCluster;
3134
import org.apache.hadoop.ozone.client.BucketArgs;
3235
import org.apache.hadoop.ozone.client.ObjectStore;
@@ -36,11 +39,15 @@
3639
import org.apache.hadoop.ozone.om.OMConfigKeys;
3740
import org.apache.hadoop.ozone.om.helpers.BucketLayout;
3841
import org.apache.hadoop.ozone.om.helpers.OmBucketInfo;
42+
import org.apache.hadoop.ozone.om.helpers.OmKeyArgs;
43+
import org.apache.hadoop.ozone.om.helpers.OmKeyLocationInfo;
3944
import org.apache.hadoop.ozone.recon.api.ContainerEndpoint;
4045
import org.apache.hadoop.ozone.recon.api.types.KeyMetadata;
4146
import org.apache.hadoop.ozone.recon.api.types.KeysResponse;
4247
import org.apache.hadoop.ozone.recon.recovery.ReconOMMetadataManager;
48+
import org.apache.hadoop.ozone.recon.spi.ReconContainerMetadataManager;
4349
import org.apache.hadoop.ozone.recon.spi.impl.OzoneManagerServiceProviderImpl;
50+
import org.apache.hadoop.ozone.recon.tasks.ContainerKeyMapperHelper;
4451
import org.apache.hadoop.ozone.recon.tasks.ReconTaskControllerImpl;
4552
import org.apache.ozone.test.GenericTestUtils;
4653
import org.junit.jupiter.api.AfterEach;
@@ -60,6 +67,9 @@ public class TestReconContainerEndpoint {
6067

6168
@BeforeEach
6269
public void init() throws Exception {
70+
// ContainerKeyMapper tasks share static maps/flags across the JVM; reset so a
71+
// prior test method cannot break mapper state for this cluster instance.
72+
ContainerKeyMapperHelper.clearSharedContainerCountMap();
6373
OzoneConfiguration conf = new OzoneConfiguration();
6474
conf.set(OMConfigKeys.OZONE_DEFAULT_BUCKET_LAYOUT,
6575
OMConfigKeys.OZONE_BUCKET_LAYOUT_FILE_SYSTEM_OPTIMIZED);
@@ -76,13 +86,9 @@ public void init() throws Exception {
7686
}
7787

7888
@AfterEach
79-
public void shutdown() throws IOException {
80-
if (client != null) {
81-
client.close();
82-
}
83-
if (cluster != null) {
84-
cluster.shutdown();
85-
}
89+
public void shutdown() {
90+
IOUtils.closeQuietly(client, cluster);
91+
ContainerKeyMapperHelper.clearSharedContainerCountMap();
8692
}
8793

8894
@Test
@@ -117,15 +123,17 @@ public void testContainerEndpointForFSOLayout() throws Exception {
117123
CompletableFuture<Void> completableFuture =
118124
omMetaManagerUtils.waitForEventBufferEmpty(reconTaskController.getEventBuffer());
119125
GenericTestUtils.waitFor(completableFuture::isDone, 100, 30000);
126+
completableFuture.join();
127+
waitUntilReconIndexesKeysForPaths(volName, bucketName,
128+
nestedDirKey, singleFileKey);
120129

121130
//Search for the bucket from the bucket table and verify its FSO
122131
OmBucketInfo bucketInfo = cluster.getOzoneManager().getBucketInfo(volName, bucketName);
123132
assertNotNull(bucketInfo);
124133
assertEquals(BucketLayout.FILE_SYSTEM_OPTIMIZED,
125134
bucketInfo.getBucketLayout());
126135

127-
// Assuming a known container ID that these keys have been written into
128-
long testContainerID = 1L;
136+
long testContainerID = getContainerIdForKey(volName, bucketName, nestedDirKey);
129137

130138
// Query the ContainerEndpoint for the keys in the specified container
131139
Response response = getContainerEndpointResponse(testContainerID);
@@ -145,7 +153,7 @@ public void testContainerEndpointForFSOLayout() throws Exception {
145153
assertEquals("file1", keyMetadata.getKey());
146154
assertEquals("testvol/fsobucket/dir1/dir2/dir3/file1", keyMetadata.getCompletePath());
147155

148-
testContainerID = 2L;
156+
testContainerID = getContainerIdForKey(volName, bucketName, singleFileKey);
149157
response = getContainerEndpointResponse(testContainerID);
150158
data = (KeysResponse) response.getEntity();
151159
keyMetadataList = data.getKeys();
@@ -186,14 +194,17 @@ public void testContainerEndpointForOBSBucket() throws Exception {
186194
CompletableFuture<Void> completableFuture =
187195
omMetaManagerUtils.waitForEventBufferEmpty(reconTaskController.getEventBuffer());
188196
GenericTestUtils.waitFor(completableFuture::isDone, 100, 30000);
197+
completableFuture.join();
198+
waitUntilReconIndexesKeysForPaths(volumeName, obsBucketName, obsSingleFileKey);
189199

190200
// Search for the bucket from the bucket table and verify its OBS
191201
OmBucketInfo bucketInfo = cluster.getOzoneManager().getBucketInfo(volumeName, obsBucketName);
192202
assertNotNull(bucketInfo);
193203
assertEquals(BucketLayout.OBJECT_STORE, bucketInfo.getBucketLayout());
194204

195-
// Initialize the ContainerEndpoint
196-
long containerId = 1L;
205+
long containerId = getContainerIdForKey(volumeName, obsBucketName,
206+
obsSingleFileKey);
207+
197208
Response response = getContainerEndpointResponse(containerId);
198209

199210
assertNotNull(response, "Response should not be null.");
@@ -226,6 +237,40 @@ private Response getContainerEndpointResponse(long containerId) {
226237
return containerEndpoint.getKeysForContainer(containerId, 10, "");
227238
}
228239

240+
/**
241+
* Wait until Recon's container-key index reflects all written keys (by container id).
242+
* The OM event queue can be empty while a batch is still being processed.
243+
*/
244+
private void waitUntilReconIndexesKeysForPaths(String volumeName,
245+
String bucketName, String... keyPaths)
246+
throws Exception {
247+
Map<Long, Integer> requiredCountByContainer = new HashMap<>();
248+
for (String keyPath : keyPaths) {
249+
long containerId =
250+
getContainerIdForKey(volumeName, bucketName, keyPath);
251+
requiredCountByContainer.merge(containerId, 1, Integer::sum);
252+
}
253+
ReconContainerMetadataManager mgr =
254+
recon.getReconServer().getReconContainerMetadataManager();
255+
TestReconOmMetaManagerUtils.waitUntilReconKeyCounts(mgr, requiredCountByContainer);
256+
}
257+
258+
private long getContainerIdForKey(String volumeName, String bucketName,
259+
String keyName) throws IOException {
260+
OmKeyArgs keyArgs = new OmKeyArgs.Builder()
261+
.setVolumeName(volumeName)
262+
.setBucketName(bucketName)
263+
.setKeyName(keyName)
264+
.build();
265+
OmKeyLocationInfo location = cluster.getOzoneManager()
266+
.lookupKey(keyArgs)
267+
.getKeyLocationVersions()
268+
.get(0)
269+
.getBlocksLatestVersionOnly()
270+
.get(0);
271+
return location.getContainerID();
272+
}
273+
229274
private void writeTestData(String volumeName, String bucketName,
230275
String keyPath, String data) throws Exception {
231276
try (OzoneOutputStream out = client.getObjectStore().getVolume(volumeName)

hadoop-ozone/integration-test-recon/src/test/java/org/apache/hadoop/ozone/recon/TestReconOmMetaManagerUtils.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@
1717

1818
package org.apache.hadoop.ozone.recon;
1919

20+
import java.io.IOException;
21+
import java.util.Map;
2022
import java.util.concurrent.CompletableFuture;
23+
import org.apache.hadoop.ozone.recon.spi.ReconContainerMetadataManager;
2124
import org.apache.hadoop.ozone.recon.tasks.OMUpdateEventBuffer;
2225
import org.apache.ozone.test.GenericTestUtils;
2326

@@ -43,4 +46,34 @@ public CompletableFuture<Void> waitForEventBufferEmpty(OMUpdateEventBuffer event
4346
}
4447
});
4548
}
49+
50+
/**
51+
* Waits until Recon's container-key index reports at least the given number of keys
52+
* per container id. Use after OM sync when the event buffer can be empty while a
53+
* dequeued batch is still being processed.
54+
* <p>
55+
* IO failures from {@code mgr} reads (including temporary {@code RocksDatabaseException}
56+
* while Recon applies updates) are treated as "not ready yet"; the wait repeats until the
57+
* timeout if counts never converge.
58+
*
59+
* @param mgr Recon container metadata manager
60+
* @param minimumCountPerContainer map of container ID to minimum inclusive key count
61+
* @throws Exception if the condition is not met within the timeout or on interrupt
62+
*/
63+
public static void waitUntilReconKeyCounts(ReconContainerMetadataManager mgr,
64+
Map<Long, Integer> minimumCountPerContainer) throws Exception {
65+
GenericTestUtils.waitFor(() -> {
66+
try {
67+
for (Map.Entry<Long, Integer> e : minimumCountPerContainer.entrySet()) {
68+
if (mgr.getKeyCountForContainer(e.getKey()) < e.getValue()) {
69+
return false;
70+
}
71+
}
72+
return true;
73+
} catch (IOException ex) {
74+
// Retry: concurrent Recon indexing can transiently expose a closed Rocks handle.
75+
return false;
76+
}
77+
}, 1000, 90000);
78+
}
4679
}

0 commit comments

Comments
 (0)