smengcl commented on code in PR #10774:
URL: https://github.com/apache/ozone/pull/10774#discussion_r3642898248


##########
hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/snapshot/defrag/TestSnapshotDefragService.java:
##########
@@ -964,6 +965,69 @@ public void testCheckAndDefragSnapshotFailure(boolean 
previousSnapshotExists) th
     }
   }
 
+  @ParameterizedTest
+  @ValueSource(booleans = {true, false})
+  public void testCheckpointCleanupOnDefragFailure(boolean 
previousSnapshotExists) throws IOException {
+    SnapshotInfo snapshotInfo = createMockSnapshotInfo(UUID.randomUUID(), 
"vol1", "bucket1", "snap2");
+    SnapshotInfo previousSnapshotInfo;
+    if (previousSnapshotExists) {
+      previousSnapshotInfo = createMockSnapshotInfo(UUID.randomUUID(), "vol1", 
"bucket1", "snap1");
+      
snapshotInfo.setPathPreviousSnapshotId(previousSnapshotInfo.getSnapshotId());
+    } else {
+      previousSnapshotInfo = null;
+    }
+
+    SnapshotChainManager chainManager = mock(SnapshotChainManager.class);
+    try (MockedStatic<SnapshotUtils> mockedStatic = 
Mockito.mockStatic(SnapshotUtils.class)) {
+      mockedStatic.when(() -> SnapshotUtils.getSnapshotInfo(eq(ozoneManager), 
eq(chainManager),
+          eq(snapshotInfo.getSnapshotId()))).thenReturn(snapshotInfo);
+      if (previousSnapshotExists) {
+        mockedStatic.when(() -> 
SnapshotUtils.getSnapshotInfo(eq(ozoneManager), eq(chainManager),
+            
eq(previousSnapshotInfo.getSnapshotId()))).thenReturn(previousSnapshotInfo);
+      }
+
+      SnapshotDefragService spyDefragService = Mockito.spy(defragService);
+      doReturn(Pair.of(true, 
10)).when(spyDefragService).needsDefragmentation(eq(snapshotInfo));
+
+      @SuppressWarnings("resource") // Mock object, no actual resource 
management needed
+      OmMetadataManagerImpl checkpointMetadataManager = 
mock(OmMetadataManagerImpl.class);
+      File checkpointPath = tempDir.resolve("checkpoint_" + 
System.nanoTime()).toAbsolutePath().toFile();
+      // Create actual checkpoint directory to verify cleanup
+      assertTrue(checkpointPath.mkdirs(), "Failed to create checkpoint 
directory for test");
+      assertTrue(checkpointPath.exists(), "Checkpoint directory should exist 
before defragmentation");
+
+      DBStore checkpointDBStore = mock(DBStore.class);
+      when(checkpointMetadataManager.getStore()).thenReturn(checkpointDBStore);
+      when(checkpointDBStore.getDbLocation()).thenReturn(checkpointPath);
+      doNothing().when(checkpointMetadataManager).close();
+      
doReturn(checkpointMetadataManager).when(spyDefragService).createCheckpoint(any(),
 any());
+
+      TablePrefixInfo prefixInfo = new TablePrefixInfo(Collections.emptyMap());
+      when(metadataManager.getTableBucketPrefix(anyString(), 
anyString())).thenReturn(prefixInfo);
+
+      // Make the defrag operation throw IOException to simulate failure
+      IOException defragException = new IOException("Defrag failed");
+      if (previousSnapshotExists) {
+        
Mockito.doThrow(defragException).when(spyDefragService).performIncrementalDefragmentation(
+            any(), any(), anyInt(), any(), any(), any());

Review Comment:
   This no longer compiles against current `master`. HDDS-15860 removed the 
snapshot-version argument from `performIncrementalDefragmentation()`, so the 
merged method takes five arguments while this stub passes six. I reproduced 
`testCompile` failing at this call. Please rebase the patch and remove 
`anyInt()`.



##########
hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/snapshot/defrag/SnapshotDefragService.java:
##########
@@ -723,7 +730,24 @@ boolean checkAndDefragSnapshot(SnapshotChainManager 
chainManager, UUID snapshotI
       }
     } finally {
       if (checkpointMetadataManager != null) {
-        checkpointMetadataManager.close();
+        try {
+          checkpointMetadataManager.close();
+        } catch (IOException closeException) {
+          LOG.error("Failed to close checkpoint metadata manager for snapshot: 
{} (ID: {})",
+              snapshotInfo.getTableKey(), snapshotInfo.getSnapshotId(), 
closeException);
+        }
+      }
+      if (!defragSuccessful && checkpointLocation.toFile().exists()) {
+        try {
+          deleteDirectory(checkpointLocation);
+          LOG.info("Cleaned up failed checkpoint directory for snapshot: {} 
(ID: {})",
+              snapshotInfo.getTableKey(), snapshotInfo.getSnapshotId());
+        } catch (IOException cleanupException) {
+          LOG.error("Failed to delete checkpoint directory {} for snapshot: {} 
(ID: {}). " +
+              "Disk space may not be freed. Manual cleanup may be required.",
+              checkpointLocation, snapshotInfo.getTableKey(), 
snapshotInfo.getSnapshotId(), cleanupException);
+          snapshotMetrics.incNumSnapshotDefragFails();

Review Comment:
   This can count one failed defragmentation twice: if cleanup fails while the 
original `IOException` is propagating, the counter is incremented here, then 
`triggerSnapshotDefragOnce()` increments the same counter again. Since this 
metric counts failed defragmentation operations, please leave the increment to 
the outer catch or use a separate cleanup-failure counter.



##########
hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/snapshot/defrag/SnapshotDefragService.java:
##########
@@ -709,7 +710,13 @@ boolean checkAndDefragSnapshot(SnapshotChainManager 
chainManager, UUID snapshotI
         checkpointMetadataManager = null;
         // Switch the snapshot DB location to the new version.
         previousVersion  = atomicSwitchSnapshotDB(snapshotId, 
checkpointLocation);
-        omSnapshotManager.deleteSnapshotCheckpointDirectories(snapshotId, 
previousVersion);
+        try {
+          omSnapshotManager.deleteSnapshotCheckpointDirectories(snapshotId, 
previousVersion);
+        } catch (IOException deleteException) {

Review Comment:
   Please preserve a retry path when deletion of the old checkpoint directories 
fails. `atomicSwitchSnapshotDB()` has already committed the new version, so 
subsequent scans may return `needsDefrag=false`. Catching the exception here 
then continues to the success metrics, with no guaranteed retry of this 
deletion; the old directories can remain until a later purge. Could cleanup be 
retried independently of whether the snapshot still needs defragmentation?



##########
hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/snapshot/defrag/SnapshotDefragService.java:
##########
@@ -661,6 +661,7 @@ boolean checkAndDefragSnapshot(SnapshotChainManager 
chainManager, UUID snapshotI
     OmMetadataManagerImpl checkpointMetadataManager = 
createCheckpoint(checkpointSnapshotInfo,
         COLUMN_FAMILIES_TO_TRACK_IN_SNAPSHOT);
     Path checkpointLocation = 
checkpointMetadataManager.getStore().getDbLocation().toPath();
+    boolean defragSuccessful = false;

Review Comment:
   Could we also cover failures inside `createCheckpoint()`? It creates the 
checkpoint directory before opening, truncating, and reopening it, but this 
`try/finally` is entered only after `createCheckpoint()` returns. An exception 
during those steps still leaves a checkpoint directory under `tmp_defrag`, so 
repeated failures can reproduce the accumulation this patch is intended to fix. 
Please clean up once the `DBCheckpoint` exists and add failure coverage for 
this path.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


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

Reply via email to