This is an automated email from the ASF dual-hosted git repository.
devmadhuu 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 46519f7fddd HDDS-15308. Improve ICR/FCR-driven container state
recovery by plugging DN report processing gaps in Recon. (#10326)
46519f7fddd is described below
commit 46519f7fdddb6f1c8ec1035f3b88f46ee3abe5cb
Author: Devesh Kumar Singh <[email protected]>
AuthorDate: Wed Jun 10 09:35:33 2026 +0530
HDDS-15308. Improve ICR/FCR-driven container state recovery by plugging DN
report processing gaps in Recon. (#10326)
---
.../ozone/recon/TestNSSummaryMemoryLeak.java | 92 +++++++++++---------
.../ozone/recon/fsck/ReconReplicationManager.java | 2 +-
.../ozone/recon/scm/ReconContainerManager.java | 28 +++---
.../scm/ReconStorageContainerManagerFacade.java | 13 ---
.../recon/scm/ReconStorageContainerSyncHelper.java | 25 ++++--
.../ozone/recon/scm/TestReconContainerManager.java | 99 +++++++++++++++++++++-
...TestReconIncrementalContainerReportHandler.java | 59 ++++++++++++-
7 files changed, 241 insertions(+), 77 deletions(-)
diff --git
a/hadoop-ozone/integration-test-recon/src/test/java/org/apache/hadoop/ozone/recon/TestNSSummaryMemoryLeak.java
b/hadoop-ozone/integration-test-recon/src/test/java/org/apache/hadoop/ozone/recon/TestNSSummaryMemoryLeak.java
index 5bdfe32aa02..fdb0a3c1204 100644
---
a/hadoop-ozone/integration-test-recon/src/test/java/org/apache/hadoop/ozone/recon/TestNSSummaryMemoryLeak.java
+++
b/hadoop-ozone/integration-test-recon/src/test/java/org/apache/hadoop/ozone/recon/TestNSSummaryMemoryLeak.java
@@ -22,6 +22,8 @@
import static
org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_BLOCK_DELETING_SERVICE_INTERVAL;
import static
org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_FS_ITERATE_BATCH_SIZE;
import static
org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_DIR_DELETING_SERVICE_INTERVAL;
+import static
org.apache.hadoop.ozone.recon.ReconServerConfigKeys.OZONE_RECON_OM_SNAPSHOT_TASK_INITIAL_DELAY;
+import static
org.apache.hadoop.ozone.recon.ReconServerConfigKeys.OZONE_RECON_OM_SNAPSHOT_TASK_INTERVAL_DELAY;
import static org.assertj.core.api.Assertions.assertThat;
import java.io.IOException;
@@ -45,8 +47,9 @@
import org.apache.hadoop.ozone.recon.recovery.ReconOMMetadataManager;
import org.apache.hadoop.ozone.recon.spi.ReconNamespaceSummaryManager;
import org.apache.hadoop.ozone.recon.spi.impl.OzoneManagerServiceProviderImpl;
+import org.apache.hadoop.ozone.recon.tasks.NSSummaryTask;
+import org.apache.hadoop.ozone.recon.tasks.ReconOmTask;
import org.apache.ozone.test.GenericTestUtils;
-import org.apache.ratis.RaftTestUtil;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
@@ -133,6 +136,8 @@ public static void init() throws Exception {
// Configure delays for testing
conf.setInt(OZONE_DIR_DELETING_SERVICE_INTERVAL, 1000000);
conf.setTimeDuration(OZONE_BLOCK_DELETING_SERVICE_INTERVAL, 10000000,
TimeUnit.MILLISECONDS);
+ conf.setTimeDuration(OZONE_RECON_OM_SNAPSHOT_TASK_INITIAL_DELAY, 1,
TimeUnit.DAYS);
+ conf.setTimeDuration(OZONE_RECON_OM_SNAPSHOT_TASK_INTERVAL_DELAY, 1,
TimeUnit.DAYS);
conf.setBoolean(OZONE_ACL_ENABLED, true);
recon = new ReconService(conf);
@@ -234,10 +239,9 @@ public void testNSSummaryCleanupOnHardDelete() throws
Exception {
// Trigger hard delete by clearing deleted tables
// This simulates the background process that hard deletes entries
simulateHardDelete(omMetadataManager);
- syncDataFromOM();
- // Verify memory leak fix - NSSummary entries should be cleaned up
- verifyNSSummaryCleanup(omMetadataManager, namespaceSummaryManager);
+ // Verify cleanup after hard delete simulation.
+ verifyNSSummaryCleanup(omMetadataManager, "memoryLeakTest");
LOG.info("NSSummary memory leak fix test completed successfully");
}
@@ -266,13 +270,11 @@ public void testNSSummaryCleanupOnHardDelete() throws
Exception {
* <li>Total: 1051 objects that will have NSSummary entries</li>
* </ul>
*
- * <p><b>Memory Usage Monitoring:</b>
- * <p>This test monitors memory usage before and after the deletion to
validate that
- * the memory leak fix prevents excessive memory consumption. The test
performs:
+ * <p><b>NSSummary Cleanup Validation:</b>
+ * <p>This test validates that NSSummary cleanup completes for a larger
+ * directory structure. The test performs:
* <ul>
- * <li>Memory measurement before deletion</li>
* <li>Directory structure deletion and hard delete simulation</li>
- * <li>Garbage collection and memory measurement after cleanup</li>
* <li>Verification that NSSummary entries are properly cleaned up</li>
* </ul>
*
@@ -291,36 +293,25 @@ public void testMemoryLeakWithLargeStructure() throws
Exception {
createDirectoryStructure(largeTestDir, numSubdirs, filesPerDir);
syncDataFromOM();
-
- // Get current memory usage
- Runtime runtime = Runtime.getRuntime();
- long memoryBefore = runtime.totalMemory() - runtime.freeMemory();
+
+ OzoneManagerServiceProviderImpl omServiceProvider =
(OzoneManagerServiceProviderImpl)
+ recon.getReconServer().getOzoneManagerServiceProvider();
+ ReconOMMetadataManager omMetadataManager =
+ (ReconOMMetadataManager)
omServiceProvider.getOMMetadataManagerInstance();
+ ReconNamespaceSummaryManager namespaceSummaryManager =
+ recon.getReconServer().getReconNamespaceSummaryManager();
+ verifyNSSummaryEntriesExist(omMetadataManager, namespaceSummaryManager,
+ numSubdirs);
// Delete and verify cleanup
fs.delete(largeTestDir, true);
syncDataFromOM();
// Simulate hard delete
- OzoneManagerServiceProviderImpl omServiceProvider =
(OzoneManagerServiceProviderImpl)
- recon.getReconServer().getOzoneManagerServiceProvider();
- ReconOMMetadataManager omMetadataManager =
- (ReconOMMetadataManager)
omServiceProvider.getOMMetadataManagerInstance();
-
simulateHardDelete(omMetadataManager);
- syncDataFromOM();
-
- // Force garbage collection
- RaftTestUtil.gc();
-
- // Verify memory cleanup
- long memoryAfter = runtime.totalMemory() - runtime.freeMemory();
- LOG.info("Memory usage - Before: {} bytes, After: {} bytes", memoryBefore,
memoryAfter);
- assertThat(memoryAfter).isLessThanOrEqualTo(memoryBefore);
- // Verify NSSummary cleanup
- ReconNamespaceSummaryManager namespaceSummaryManager =
- recon.getReconServer().getReconNamespaceSummaryManager();
- verifyNSSummaryCleanup(omMetadataManager, namespaceSummaryManager);
+ // Verify cleanup after hard delete simulation.
+ verifyNSSummaryCleanup(omMetadataManager, "largeMemoryLeakTest");
LOG.info("Large structure memory leak test completed successfully");
}
@@ -380,10 +371,13 @@ private void createDirectoryStructure(Path rootDir, int
numSubdirs, int filesPer
*
* @throws IOException if synchronization fails
*/
- private void syncDataFromOM() throws IOException {
+ private void syncDataFromOM() throws Exception {
OzoneManagerServiceProviderImpl impl = (OzoneManagerServiceProviderImpl)
recon.getReconServer().getOzoneManagerServiceProvider();
impl.syncDataFromOM();
+ GenericTestUtils.waitFor(
+ () -> NSSummaryTask.getRebuildState() !=
NSSummaryTask.RebuildState.RUNNING,
+ 100, 60000);
}
private void verifyNSSummaryEntriesExist(ReconOMMetadataManager
omMetadataManager,
@@ -445,15 +439,15 @@ private void
verifyEntriesInDeletedTables(ReconOMMetadataManager omMetadataManag
* <p>This simulation:
* <ol>
* <li>Iterates through all entries in deletedDirTable</li>
- * <li>Deletes each entry to trigger the memory leak fix</li>
- * <li>The deletion triggers {@code
NSSummaryTaskWithFSO.handleUpdateOnDeletedDirTable()}</li>
- * <li>Which in turn cleans up the corresponding NSSummary entries</li>
+ * <li>Deletes each entry from the deleted directory table</li>
+ * <li>Reprocesses NSSummary from the current Recon OM metadata
snapshot</li>
* </ol>
*
* @param omMetadataManager the metadata manager containing the deleted
tables
* @throws IOException if table operations fail
*/
- private void simulateHardDelete(ReconOMMetadataManager omMetadataManager)
throws IOException {
+ private void simulateHardDelete(ReconOMMetadataManager omMetadataManager)
+ throws IOException {
// Simulate hard delete by clearing deleted tables
Table<String, OmKeyInfo> deletedDirTable =
omMetadataManager.getDeletedDirTable();
@@ -464,29 +458,43 @@ private void simulateHardDelete(ReconOMMetadataManager
omMetadataManager) throws
deletedDirTable.delete(kv.getKey());
}
}
+ reprocessNSSummary(omMetadataManager);
+ }
+
+ private void reprocessNSSummary(ReconOMMetadataManager omMetadataManager) {
+ ReconOmTask nsSummaryTask = recon.getReconServer().getReconTaskController()
+ .getRegisteredTasks().get("NSSummaryTask");
+ assertThat(nsSummaryTask).isNotNull();
+ ReconOmTask.TaskResult result = nsSummaryTask.reprocess(omMetadataManager);
+ assertThat(result.isTaskSuccess()).isTrue();
}
private void verifyNSSummaryCleanup(ReconOMMetadataManager omMetadataManager,
- ReconNamespaceSummaryManager namespaceSummaryManager) throws Exception {
+ String path) throws Exception {
// Wait for cleanup to complete
GenericTestUtils.waitFor(() -> {
try {
- // Check that deleted directories don't have NSSummary entries
+ // Check that simulated hard delete drained the deleted directory
table.
Table<String, OmDirectoryInfo> dirTable =
omMetadataManager.getDirectoryTable();
+ Table<String, OmKeyInfo> deletedDirTable =
omMetadataManager.getDeletedDirTable();
+
+ if (omMetadataManager.countRowsInTable(deletedDirTable) != 0) {
+ return false;
+ }
// Verify that the main test directory is no longer in the directory
table
try (Table.KeyValueIterator<String, OmDirectoryInfo> iterator =
dirTable.iterator()) {
while (iterator.hasNext()) {
Table.KeyValue<String, OmDirectoryInfo> kv = iterator.next();
- String path = kv.getKey();
- if (path.contains("memoryLeakTest")) {
- LOG.info("Found test directory still in table: {}", path);
+ String key = kv.getKey();
+ if (key.contains(path)) {
+ LOG.info("Found test directory still in table: {}", key);
return false;
}
}
}
-
+
return true;
} catch (Exception e) {
LOG.error("Error verifying cleanup", e);
diff --git
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/fsck/ReconReplicationManager.java
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/fsck/ReconReplicationManager.java
index 8f56ddc8f2f..09005cadb18 100644
---
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/fsck/ReconReplicationManager.java
+++
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/fsck/ReconReplicationManager.java
@@ -322,7 +322,7 @@ public synchronized void processAll() {
processedCount++;
if (processedCount % logEvery == 0 || processedCount ==
containers.size()) {
- LOG.info("Processed {}/{} containers", processedCount,
containers.size());
+ LOG.debug("Processed {}/{} containers", processedCount,
containers.size());
}
} catch (ContainerNotFoundException e) {
LOG.error("Container {} not found", container.getContainerID(), e);
diff --git
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/scm/ReconContainerManager.java
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/scm/ReconContainerManager.java
index 4057093c3aa..25d8543ab27 100644
---
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/scm/ReconContainerManager.java
+++
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/scm/ReconContainerManager.java
@@ -201,6 +201,7 @@ public void checkAndAddNewContainerBatch(
void transitionOpenToClosing(ContainerID containerID, ContainerInfo
containerInfo)
throws IOException, InvalidStateTransitionException {
PipelineID pipelineID = containerInfo.getPipelineID();
+ updateContainerState(containerID, FINALIZE); // OPEN → CLOSING
if (pipelineID != null) {
int curCnt = pipelineToOpenContainer.getOrDefault(pipelineID, 0);
if (curCnt == 1) {
@@ -209,12 +210,20 @@ void transitionOpenToClosing(ContainerID containerID,
ContainerInfo containerInf
pipelineToOpenContainer.put(pipelineID, curCnt - 1);
}
}
- updateContainerState(containerID, FINALIZE); // OPEN → CLOSING
}
/**
- * Check if an OPEN container should move to CLOSING based on a healthy
- * non-OPEN DN replica report.
+ * Check if Recon's container lifecycle state needs the Recon-specific
+ * pre-processing required before SCM's shared report handler processes the
+ * replica.
+ *
+ * <p>Recon only handles OPEN to CLOSING here to keep the per-pipeline open
+ * container count accurate. All other known-container lifecycle transitions
+ * are left to SCM's common ICR/FCR state machine, which is invoked after
this
+ * method by Recon's report handlers.
+ *
+ * @param containerID containerID to check
+ * @param replicaState replica state reported by a DataNode
*/
private void checkContainerStateAndUpdate(ContainerID containerID,
ContainerReplicaProto.State
replicaState)
@@ -222,15 +231,12 @@ private void checkContainerStateAndUpdate(ContainerID
containerID,
ContainerInfo containerInfo = getContainer(containerID);
HddsProtos.LifeCycleState reconState = containerInfo.getState();
- if (reconState != HddsProtos.LifeCycleState.OPEN
- || replicaState == ContainerReplicaProto.State.OPEN
- || !isHealthy(replicaState)) {
- return;
+ if (reconState == HddsProtos.LifeCycleState.OPEN
+ && replicaState != ContainerReplicaProto.State.OPEN &&
isHealthy(replicaState)) {
+ LOG.info("Container {} has state OPEN, but given state is {}.",
+ containerID, replicaState);
+ transitionOpenToClosing(containerID, containerInfo);
}
-
- LOG.info("Container {} is OPEN in Recon but DN reports replica state {}. "
- + "Moving to CLOSING.", containerID, replicaState);
- transitionOpenToClosing(containerID, containerInfo);
}
private boolean isHealthy(ContainerReplicaProto.State replicaState) {
diff --git
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/scm/ReconStorageContainerManagerFacade.java
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/scm/ReconStorageContainerManagerFacade.java
index 1712e711fe0..679e3c3c71e 100644
---
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/scm/ReconStorageContainerManagerFacade.java
+++
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/scm/ReconStorageContainerManagerFacade.java
@@ -896,19 +896,6 @@ public boolean triggerTargetedSCMContainerSync() {
}
}
- public boolean syncWithSCMContainerInfo() {
- if (isSyncDataFromSCMRunning.compareAndSet(false, true)) {
- try {
- return runTargetedSyncWithMetrics();
- } finally {
- isSyncDataFromSCMRunning.compareAndSet(true, false);
- }
- } else {
- LOG.debug("SCM DB sync is already running.");
- return false;
- }
- }
-
private boolean runTargetedSyncWithMetrics() {
long startTime = Time.monotonicNow();
containerSyncMetrics.setTargetedSyncStatus(
diff --git
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/scm/ReconStorageContainerSyncHelper.java
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/scm/ReconStorageContainerSyncHelper.java
index 9a1aa48a1e1..bbd03aab0d5 100644
---
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/scm/ReconStorageContainerSyncHelper.java
+++
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/scm/ReconStorageContainerSyncHelper.java
@@ -90,6 +90,7 @@ class ReconStorageContainerSyncHelper {
* (DELETED ID list).
*/
private static final long CONTAINER_ID_PROTO_SIZE_BYTES = 12;
+ private static final long DELETED_SYNC_TRANSITION_LOG_SAMPLE_INTERVAL =
1000L;
/**
* Conservative wire-size upper bound for one {@code ContainerWithPipeline}
@@ -365,6 +366,7 @@ private boolean syncDeletedContainers() {
OZONE_RECON_SCM_DELETED_CONTAINER_CHECK_BATCH_SIZE_DEFAULT);
int batchSize = (int) getContainerCountPerCall(configuredBatch);
int retiredCount = 0;
+ long processedCount = 0;
// Existing Recon containers need only the ID to retire to DELETED. Fetch
// full ContainerInfo only for IDs absent from Recon, where we must add a
@@ -380,7 +382,8 @@ private boolean syncDeletedContainers() {
if (page == null || page.isEmpty()) {
break;
}
- retiredCount += processDeletedPage(page);
+ retiredCount += processDeletedPage(page, processedCount);
+ processedCount += page.size();
start = ContainerID.valueOf(
page.get(page.size() - 1).getId() + 1);
}
@@ -403,9 +406,12 @@ private boolean syncDeletedContainers() {
* <li>If already DELETED in Recon: no-op.</li>
* </ul>
*/
- private int processDeletedPage(List<ContainerID> page) {
+ private int processDeletedPage(List<ContainerID> page,
+ long processedCountBeforePage) {
int retiredCount = 0;
+ long processedCount = processedCountBeforePage;
for (ContainerID containerID : page) {
+ processedCount++;
if (!containerManager.containerExist(containerID)) {
if (addContainerInfoFallback(containerID,
HddsProtos.LifeCycleState.DELETED, "DELETED sync")) {
@@ -417,7 +423,7 @@ private int processDeletedPage(List<ContainerID> page) {
ContainerInfo reconInfo = containerManager.getContainer(containerID);
if (reconInfo.getState() != HddsProtos.LifeCycleState.DELETED) {
retireContainerToDeleted(containerID, reconInfo,
- HddsProtos.LifeCycleState.DELETED);
+ HddsProtos.LifeCycleState.DELETED, processedCount);
retiredCount++;
}
// reconState == DELETED: already terminal, nothing to do.
@@ -464,10 +470,13 @@ private int processDeletedPage(List<ContainerID> page) {
* @param reconInfo current Recon snapshot of the container (used for
* OPEN→CLOSING transition and log messages)
* @param scmState always DELETED (passed through to log messages)
+ * @param processedCount current number of SCM DELETED IDs scanned in this
+ * sync cycle
*/
private void retireContainerToDeleted(ContainerID containerID,
ContainerInfo reconInfo,
- HddsProtos.LifeCycleState scmState) {
+ HddsProtos.LifeCycleState scmState,
+ long processedCount) {
try {
HddsProtos.LifeCycleState reconState = reconInfo.getState();
@@ -486,9 +495,11 @@ private void retireContainerToDeleted(ContainerID
containerID,
// DELETING → DELETED.
containerManager.updateContainerState(containerID, CLEANUP);
- LOG.info("DELETED sync: container {} transitioned "
- + "{} → DELETED in Recon (SCM state: {}).",
- containerID, reconInfo.getState(), scmState);
+ if (processedCount % DELETED_SYNC_TRANSITION_LOG_SAMPLE_INTERVAL == 0) {
+ LOG.debug("DELETED sync: container {} transitioned "
+ + "{} → DELETED in Recon (SCM state: {}).",
+ containerID, reconInfo.getState(), scmState);
+ }
} catch (InvalidStateTransitionException | IOException e) {
LOG.warn("DELETED sync: failed to retire container {} "
+ "from {} toward DELETED.", containerID, reconInfo.getState(), e);
diff --git
a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/scm/TestReconContainerManager.java
b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/scm/TestReconContainerManager.java
index e1e89f180be..8eade310b9b 100644
---
a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/scm/TestReconContainerManager.java
+++
b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/scm/TestReconContainerManager.java
@@ -25,7 +25,10 @@
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.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
import java.io.IOException;
import java.util.LinkedList;
@@ -43,6 +46,7 @@
import org.apache.hadoop.hdds.scm.container.ContainerChecksums;
import org.apache.hadoop.hdds.scm.container.ContainerID;
import org.apache.hadoop.hdds.scm.container.ContainerInfo;
+import org.apache.hadoop.hdds.scm.container.ContainerNotFoundException;
import org.apache.hadoop.hdds.scm.container.ContainerReplica;
import
org.apache.hadoop.hdds.scm.container.common.helpers.ContainerWithPipeline;
import org.apache.hadoop.hdds.scm.ha.SCMHAManager;
@@ -175,12 +179,103 @@ public void testUpdateContainerStateFromOpen() throws
Exception {
DatanodeDetails datanodeDetails = randomDatanodeDetails();
- // First report with "CLOSED" replica state moves container state to
+ // First report with "CLOSING" replica state moves container state to
// "CLOSING".
- getContainerManager().checkAndAddNewContainer(containerID, State.CLOSED,
+ getContainerManager().checkAndAddNewContainer(containerID, State.CLOSING,
datanodeDetails);
assertEquals(CLOSING,
getContainerManager().getContainer(containerID).getState());
+ assertFalse(getContainerManager().getPipelineToOpenContainer()
+ .containsKey(containerWithPipeline.getPipeline().getId()));
+ }
+
+ @Test
+ public void testOpenContainerTransitionsToClosingWithoutScmLookup()
+ throws Exception {
+ ContainerWithPipeline openContainer =
+ getTestContainer(113L, LifeCycleState.OPEN);
+ ContainerID containerID = openContainer.getContainerInfo().containerID();
+ getContainerManager().addNewContainer(openContainer);
+
+ getContainerManager().checkAndAddNewContainer(containerID, State.CLOSED,
+ randomDatanodeDetails());
+
+ assertEquals(CLOSING,
+ getContainerManager().getContainer(containerID).getState());
+ assertFalse(getContainerManager().getPipelineToOpenContainer()
+ .containsKey(openContainer.getPipeline().getId()));
+ verify(getContainerManager().getScmClient(), never())
+ .getContainerWithPipeline(containerID.getId());
+ }
+
+ @Test
+ public void testTransitionOpenToClosingDoesNotDecrementCountOnFailure()
+ throws Exception {
+ ContainerWithPipeline missingContainer =
+ getTestContainer(114L, LifeCycleState.OPEN);
+ ContainerID containerID =
+ missingContainer.getContainerInfo().containerID();
+ Pipeline pipeline = missingContainer.getPipeline();
+ getContainerManager().getPipelineToOpenContainer().put(pipeline.getId(),
1);
+
+ assertThrows(ContainerNotFoundException.class,
+ () -> getContainerManager().transitionOpenToClosing(containerID,
+ missingContainer.getContainerInfo()));
+
+ assertEquals(1,
+ getContainerManager().getPipelineToOpenContainer()
+ .get(pipeline.getId()));
+ }
+
+ @Test
+ public void testOpenContainerNotUpdatedFromUnhealthyReplicaReports()
+ throws Exception {
+ for (State replicaState : new State[] {
+ State.UNHEALTHY, State.INVALID, State.DELETED}) {
+ ContainerWithPipeline containerWithPipeline =
+ getTestContainer(120L + replicaState.ordinal(), LifeCycleState.OPEN);
+ ContainerID containerID =
+ containerWithPipeline.getContainerInfo().containerID();
+ getContainerManager().addNewContainer(containerWithPipeline);
+
+ getContainerManager().checkAndAddNewContainer(containerID, replicaState,
+ randomDatanodeDetails());
+
+ assertEquals(LifeCycleState.OPEN,
+ getContainerManager().getContainer(containerID).getState());
+ assertTrue(getContainerManager().getPipelineToOpenContainer()
+ .containsKey(containerWithPipeline.getPipeline().getId()));
+ }
+ }
+
+ @Test
+ public void testClosingContainerNotUpdatedFromUnhealthyReplicaReport()
+ throws Exception {
+ ContainerWithPipeline closingContainer = getTestContainer(105L, CLOSING);
+ ContainerID containerID =
closingContainer.getContainerInfo().containerID();
+ getContainerManager().addNewContainer(closingContainer);
+
+ getContainerManager().checkAndAddNewContainer(containerID, State.UNHEALTHY,
+ randomDatanodeDetails());
+
+ assertEquals(CLOSING,
+ getContainerManager().getContainer(containerID).getState());
+ }
+
+ @Test
+ public void testOtherReconStatesDoNotInferDnReplicaTransition()
+ throws Exception {
+ ContainerWithPipeline closedContainer = getTestContainer(112L, CLOSED);
+ ContainerID containerID = closedContainer.getContainerInfo().containerID();
+ getContainerManager().addNewContainer(closedContainer);
+
+ getContainerManager().checkAndAddNewContainer(containerID,
+ State.CLOSING, randomDatanodeDetails());
+
+ assertEquals(CLOSED,
+ getContainerManager().getContainer(containerID).getState());
+ verify(getContainerManager().getScmClient(), never())
+ .getContainerWithPipeline(containerID.getId());
}
ContainerInfo newContainerInfo(long containerId, Pipeline pipeline) {
diff --git
a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/scm/TestReconIncrementalContainerReportHandler.java
b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/scm/TestReconIncrementalContainerReportHandler.java
index 78f9bbceafc..c82eabb92e7 100644
---
a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/scm/TestReconIncrementalContainerReportHandler.java
+++
b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/scm/TestReconIncrementalContainerReportHandler.java
@@ -24,6 +24,8 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.io.IOException;
@@ -129,6 +131,7 @@ public void testProcessICRStateMismatch()
containerId++, OPEN);
ContainerID containerID =
containerWithPipeline.getContainerInfo().containerID();
+ LifeCycleState expectedState = getContainerStateFromReplicaState(state);
ReconContainerManager containerManager = getContainerManager();
containerManager.addNewContainer(containerWithPipeline);
@@ -155,12 +158,46 @@ public void testProcessICRStateMismatch()
assertTrue(containerManager.containerExist(containerID));
assertEquals(1,
containerManager.getContainerReplicas(containerID).size());
- LifeCycleState expectedState = getContainerStateFromReplicaState(state);
LifeCycleState actualState =
containerManager.getContainer(containerID).getState();
assertEquals(expectedState, actualState,
String.format("Expecting %s in container state for replica state %s",
expectedState, state));
+ verify(containerManager.getScmClient(), never())
+ .getContainerWithPipeline(containerID.getId());
+ }
+ }
+
+ @Test
+ public void testClosingContainerAdvancesViaScmHandlerWithoutScmLookup()
+ throws IOException, NodeNotFoundException, TimeoutException {
+ long containerId = 200;
+ for (State state : Arrays.asList(State.QUASI_CLOSED, State.CLOSED)) {
+ ContainerWithPipeline containerWithPipeline =
+ getTestContainer(containerId++, LifeCycleState.CLOSING);
+ ContainerID containerID =
+ containerWithPipeline.getContainerInfo().containerID();
+ LifeCycleState expectedState = getContainerStateFromReplicaState(state);
+ ReconContainerManager containerManager = getContainerManager();
+ containerManager.addNewContainer(containerWithPipeline);
+
+ DatanodeDetails datanodeDetails =
+ containerWithPipeline.getPipeline().getFirstNode();
+ ReconIncrementalContainerReportHandler reconIcr =
+ new ReconIncrementalContainerReportHandler(
+ getNodeManagerMock(datanodeDetails), containerManager,
+ SCMContext.emptyContext());
+ IncrementalContainerReportFromDatanode reportMock =
+ getReportMock(containerID, state, datanodeDetails);
+
+ reconIcr.onMessage(reportMock, mock(EventPublisher.class));
+
+ assertEquals(expectedState,
+ containerManager.getContainer(containerID).getState(),
+ String.format("Expecting %s in container state for replica state %s",
+ expectedState, state));
+ verify(containerManager.getScmClient(), never())
+ .getContainerWithPipeline(containerID.getId());
}
}
@@ -199,6 +236,26 @@ private LifeCycleState getContainerStateFromReplicaState(
}
}
+ private static NodeManager getNodeManagerMock(DatanodeDetails
datanodeDetails)
+ throws NodeNotFoundException {
+ NodeManager nodeManagerMock = mock(NodeManager.class);
+ when(nodeManagerMock.getNode(any(DatanodeID.class)))
+ .thenReturn(datanodeDetails);
+ return nodeManagerMock;
+ }
+
+ private static IncrementalContainerReportFromDatanode getReportMock(
+ ContainerID containerID, State state, DatanodeDetails datanodeDetails) {
+ IncrementalContainerReportFromDatanode reportMock =
+ mock(IncrementalContainerReportFromDatanode.class);
+ when(reportMock.getDatanodeDetails()).thenReturn(datanodeDetails);
+ IncrementalContainerReportProto containerReport =
+ getIncrementalContainerReportProto(containerID, state,
+ datanodeDetails.getUuidString());
+ when(reportMock.getReport()).thenReturn(containerReport);
+ return reportMock;
+ }
+
private static IncrementalContainerReportProto
getIncrementalContainerReportProto(final ContainerID containerId,
final State state,
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]