diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/ContainerEndpoint.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/ContainerEndpoint.java index 49fab38fcf2..c1818b84fae 100644 --- a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/ContainerEndpoint.java +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/ContainerEndpoint.java @@ -659,11 +659,11 @@ public Response getContainerMisMatchInsights( } } - List pipelines = new ArrayList<>(); nonOMContainers.forEach(containerInfo -> { ContainerDiscrepancyInfo containerDiscrepancyInfo = new ContainerDiscrepancyInfo(); containerDiscrepancyInfo.setContainerID(containerInfo.getContainerID()); containerDiscrepancyInfo.setNumberOfKeys(0); + List pipelines = new ArrayList<>(); PipelineID pipelineID = null; try { pipelineID = containerInfo.getPipelineID(); diff --git a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/api/TestContainerEndpoint.java b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/api/TestContainerEndpoint.java index 3b0764ba3fc..19f1593db66 100644 --- a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/api/TestContainerEndpoint.java +++ b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/api/TestContainerEndpoint.java @@ -26,8 +26,10 @@ import static org.apache.hadoop.ozone.recon.OMMetadataManagerTestUtils.writeKeyToOm; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -1596,6 +1598,102 @@ public void testContainerMissingFilter() assertThat(missingContainerIdsSCM).contains(2L); } + /** + * Helper to create a container in SCM with a specific pipeline. + */ + private void createContainerInSCM(long containerId, Pipeline targetPipeline) + throws IOException, TimeoutException { + ContainerInfo containerInfo = new ContainerInfo.Builder() + .setContainerID(containerId) + .setReplicationConfig( + RatisReplicationConfig.getInstance(ReplicationFactor.THREE)) + .setState(HddsProtos.LifeCycleState.OPEN) + .setOwner("owner" + containerId) + .setNumberOfKeys(0) + .setPipelineID(targetPipeline.getId()) + .build(); + reconContainerManager.addNewContainer( + new ContainerWithPipeline(containerInfo, targetPipeline)); + } + + /** + * Helper to verify pipeline isolation for a container missing in OM. + */ + private void verifyPipelineIsolation(ContainerDiscrepancyInfo info, + long containerId, PipelineID expectedPipelineId, + List> otherPipelineLists) { + List pipelines = info.getPipelines(); + assertNotNull(pipelines); + assertEquals(1, pipelines.size(), + "Container " + containerId + " should have exactly 1 pipeline"); + assertEquals(expectedPipelineId, pipelines.get(0).getId(), + "Container " + containerId + " should have correct pipeline"); + assertEquals("SCM", info.getExistsAt()); + assertEquals(0, info.getNumberOfKeys()); + // Verify list isolation + for (List other : otherPipelineLists) { + if (other != null) { + assertNotSame(pipelines, other, + "Container " + containerId + " should have independent list"); + } + } + } + + @Test + public void testGetContainerInsightsNonOMContainersPipelineIsolation() + throws IOException, TimeoutException { + // Verifies fix for pipeline accumulation bug: containers missing in OM + // should each have their own isolated pipeline list, not shared. + + // Create 3 different pipelines + Pipeline[] pipelines = { + getRandomPipeline(), getRandomPipeline(), getRandomPipeline() + }; + for (Pipeline p : pipelines) { + reconPipelineManager.addPipeline(p); + } + + // Create 3 containers in SCM with different pipelines (not in OM) + long[] containerIds = {501L, 502L, 503L}; + for (int i = 0; i < containerIds.length; i++) { + createContainerInSCM(containerIds[i], pipelines[i]); + assertFalse(reconContainerMetadataManager.doesContainerExists(containerIds[i]), + "Container " + containerIds[i] + " should NOT exist in OM"); + } + + // Call API + Response response = containerEndpoint.getContainerMisMatchInsights(10, 500, "OM"); + assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); + + Map responseMap = (Map) response.getEntity(); + List discrepancies = + (List) responseMap.get("containerDiscrepancyInfo"); + assertNotNull(discrepancies); + + // Find containers in response + ContainerDiscrepancyInfo[] infos = new ContainerDiscrepancyInfo[3]; + for (int i = 0; i < containerIds.length; i++) { + final long id = containerIds[i]; + infos[i] = discrepancies.stream() + .filter(d -> d.getContainerID() == id) + .findFirst() + .orElseThrow(() -> new AssertionError( + "Container " + id + " not found in mismatch list")); + } + + // Verify pipeline isolation for each container + List pipelineList1 = infos[0].getPipelines(); + List pipelineList2 = infos[1].getPipelines(); + List pipelineList3 = infos[2].getPipelines(); + + verifyPipelineIsolation(infos[0], containerIds[0], pipelines[0].getId(), + Arrays.asList(pipelineList2, pipelineList3)); + verifyPipelineIsolation(infos[1], containerIds[1], pipelines[1].getId(), + Arrays.asList(pipelineList1, pipelineList3)); + verifyPipelineIsolation(infos[2], containerIds[2], pipelines[2].getId(), + Arrays.asList(pipelineList1, pipelineList2)); + } + @Test public void testGetOmContainersDeletedInSCM() throws Exception { Map omContainers =