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 95b4fdcf72 HDDS-12122. Add unit test for SnapshotChainRepair (#7741)
95b4fdcf72 is described below

commit 95b4fdcf7243de24cfbd0c7d34f4f0332dab7c5d
Author: Peter Lee <[email protected]>
AuthorDate: Sat Jan 25 19:50:35 2025 +0800

    HDDS-12122. Add unit test for SnapshotChainRepair (#7741)
---
 .../ozone/repair/om/TestSnapshotChainRepair.java   | 376 +++++++++++++++++++++
 1 file changed, 376 insertions(+)

diff --git 
a/hadoop-ozone/tools/src/test/java/org/apache/hadoop/ozone/repair/om/TestSnapshotChainRepair.java
 
b/hadoop-ozone/tools/src/test/java/org/apache/hadoop/ozone/repair/om/TestSnapshotChainRepair.java
new file mode 100644
index 0000000000..ba96742c23
--- /dev/null
+++ 
b/hadoop-ozone/tools/src/test/java/org/apache/hadoop/ozone/repair/om/TestSnapshotChainRepair.java
@@ -0,0 +1,376 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.hadoop.ozone.repair.om;
+
+import org.apache.hadoop.hdds.utils.IOUtils;
+import org.apache.hadoop.hdds.utils.db.StringCodec;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksDB;
+import org.apache.hadoop.ozone.debug.RocksDBUtils;
+import org.apache.hadoop.ozone.om.helpers.SnapshotInfo;
+import org.apache.hadoop.ozone.repair.OzoneRepair;
+import org.apache.ozone.test.GenericTestUtils;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.mockito.MockedStatic;
+import org.rocksdb.ColumnFamilyHandle;
+import org.rocksdb.RocksDB;
+import org.rocksdb.RocksIterator;
+import org.rocksdb.ColumnFamilyDescriptor;
+
+import picocli.CommandLine;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
+
+import static org.apache.ozone.test.IntLambda.withTextFromSystemIn;
+import static org.apache.hadoop.ozone.OzoneConsts.SNAPSHOT_INFO_TABLE;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests SnapshotChainRepair.
+ */
+public class TestSnapshotChainRepair {
+
+  private ManagedRocksDB managedRocksDB;
+  private RocksDB rocksDB;
+  private ColumnFamilyHandle columnFamilyHandle;
+
+  private static final String DB_PATH = "testDBPath";
+
+  private MockedStatic<ManagedRocksDB> mockedDB;
+  private MockedStatic<RocksDBUtils> mockedUtils;
+
+  private GenericTestUtils.PrintStreamCapturer out;
+  private GenericTestUtils.PrintStreamCapturer err;
+
+  @BeforeEach
+  public void setup() throws Exception {
+    out = GenericTestUtils.captureOut();
+    err = GenericTestUtils.captureErr();
+
+    // Initialize static mocks
+    mockedDB = mockStatic(ManagedRocksDB.class);
+    mockedUtils = mockStatic(RocksDBUtils.class);
+  }
+
+  @AfterEach
+  public void tearDown() {
+    IOUtils.closeQuietly(out, err);
+
+    if (mockedDB != null) {
+      mockedDB.close();
+    }
+    if (mockedUtils != null) {
+      mockedUtils.close();
+    }
+  }
+
+  private void setupMockDB(SnapshotInfo snapshotInfo,
+      List<SnapshotInfo> iteratorSnapshots) throws Exception {
+
+    managedRocksDB = mock(ManagedRocksDB.class);
+    rocksDB = mock(RocksDB.class);
+    columnFamilyHandle = mock(ColumnFamilyHandle.class);
+
+    when(managedRocksDB.get()).thenReturn(rocksDB);
+
+    // Mock column family descriptors
+    List<ColumnFamilyDescriptor> cfDescList = new ArrayList<>();
+    cfDescList.add(new ColumnFamilyDescriptor(new byte[] {1}));
+
+    mockedUtils.when(() -> 
RocksDBUtils.getColumnFamilyDescriptors(eq(DB_PATH)))
+        .thenReturn(cfDescList);
+
+    // Mock DB open
+    mockedDB.when(() -> ManagedRocksDB.open(eq(DB_PATH), eq(cfDescList), 
eq(new ArrayList<>())))
+        .thenReturn(managedRocksDB);
+
+    // Mock column family handle
+    mockedUtils.when(() -> RocksDBUtils.getColumnFamilyHandle(
+        eq(SNAPSHOT_INFO_TABLE), anyList()))
+        .thenReturn(columnFamilyHandle);
+
+    // Mock snapshot retrieval
+    mockedUtils.when(() -> RocksDBUtils.getValue(
+        eq(managedRocksDB),
+        eq(columnFamilyHandle),
+        anyString(),
+        eq(SnapshotInfo.getCodec())))
+        .thenReturn(snapshotInfo);
+
+    // Mock iterator
+    RocksIterator rocksIterator = mock(RocksIterator.class);
+    when(rocksDB.newIterator(columnFamilyHandle)).thenReturn(rocksIterator);
+
+    // Setup iterator behavior based on provided snapshots
+    if (iteratorSnapshots.isEmpty()) {
+      when(rocksIterator.isValid()).thenReturn(false);
+    } else {
+      Boolean[] remainingValidResponses = new 
Boolean[iteratorSnapshots.size()];
+      for (int i = 0; i < iteratorSnapshots.size() - 1; i++) {
+        remainingValidResponses[i] = true;
+      }
+      remainingValidResponses[iteratorSnapshots.size() - 1] = false;
+
+      when(rocksIterator.isValid())
+          .thenReturn(true, remainingValidResponses);
+
+      ArrayList<byte[]> valueResponses = new ArrayList<>();
+      for (SnapshotInfo snap : iteratorSnapshots) {
+        try {
+          valueResponses.add(SnapshotInfo.getCodec().toPersistedFormat(snap));
+        } catch (IOException e) {
+          Assertions.fail("Failed to serialize snapshot info");
+        }
+      }
+      byte[] firstValue = valueResponses.get(0);
+      byte[][] remainingValueResponses = valueResponses.subList(1, 
valueResponses.size()).toArray(new byte[0][]);
+      when(rocksIterator.value())
+          .thenReturn(firstValue, remainingValueResponses);
+    }
+  }
+
+  @ParameterizedTest
+  @ValueSource(booleans = {true, false})
+  public void testSuccessfulRepair(boolean dryRun) throws Exception {
+    String volumeName = "vol1";
+    String bucketName = "bucket1";
+    String snapshotName = "snap1";
+    String globalPrevSnapshotName = "global-prev-snap1";
+    String pathPrevSnapshotName = "path-prev-snap1";
+
+    UUID snapshotId = UUID.randomUUID();
+    UUID globalPrevSnapshotId = UUID.randomUUID();
+    UUID pathPrevSnapshotId = UUID.randomUUID();
+
+    SnapshotInfo snapshotInfo = SnapshotInfo.newInstance(volumeName, 
bucketName, snapshotName, snapshotId, 0);
+    SnapshotInfo globalPrevSnapshot = SnapshotInfo.newInstance(volumeName, 
bucketName, globalPrevSnapshotName,
+        globalPrevSnapshotId, 0);
+    SnapshotInfo pathPrevSnapshot = SnapshotInfo.newInstance(volumeName, 
bucketName, pathPrevSnapshotName,
+        pathPrevSnapshotId, 0);
+
+    List<SnapshotInfo> iteratorSnapshots = Arrays.asList(
+        snapshotInfo, globalPrevSnapshot, pathPrevSnapshot);
+
+    List<String> argsList = new ArrayList<>(Arrays.asList(
+        "om", "snapshot", "chain",
+        volumeName + "/" + bucketName,
+        snapshotName,
+        "--db", DB_PATH,
+        "--global-previous", globalPrevSnapshotId.toString(),
+        "--path-previous", pathPrevSnapshotId.toString()));
+
+    if (dryRun) {
+      argsList.add("--dry-run");
+    }
+
+    setupMockDB(snapshotInfo, iteratorSnapshots);
+
+    CommandLine cli = new OzoneRepair().getCmd();
+    withTextFromSystemIn("y")
+        .execute(() -> cli.execute(argsList.toArray(new String[0])));
+
+    String output = out.getOutput();
+    assertTrue(output.contains("Updating SnapshotInfo to"));
+
+    if (dryRun) {
+      // Verify DB update was NOT called in dry run mode
+      verify(rocksDB, never()).put(
+          eq(columnFamilyHandle),
+          eq(StringCodec.get().toPersistedFormat(snapshotInfo.getTableKey())),
+          eq(SnapshotInfo.getCodec().toPersistedFormat(snapshotInfo)));
+    } else {
+      // Verify DB update was called with correct parameters
+      verify(rocksDB).put(
+          eq(columnFamilyHandle),
+          eq(StringCodec.get().toPersistedFormat(snapshotInfo.getTableKey())),
+          eq(SnapshotInfo.getCodec().toPersistedFormat(snapshotInfo)));
+      assertTrue(output.contains("Snapshot Info is updated"));
+    }
+  }
+
+  @Test
+  public void testGlobalPreviousMatchesSnapshotId() throws Exception {
+    String volumeName = "vol1";
+    String bucketName = "bucket1";
+    String snapshotName = "snap1";
+
+    UUID snapshotId = UUID.randomUUID();
+    // Use same ID for global previous to trigger error
+    UUID globalPrevSnapshotId = snapshotId;
+    UUID pathPrevSnapshotId = UUID.randomUUID();
+
+    SnapshotInfo snapshotInfo = SnapshotInfo.newInstance(volumeName, 
bucketName,
+        snapshotName, snapshotId, 0);
+    SnapshotInfo pathPrevSnapshot = SnapshotInfo.newInstance(volumeName, 
bucketName,
+        "path-prev", pathPrevSnapshotId, 0);
+
+    List<SnapshotInfo> iteratorSnapshots = Arrays.asList(
+        snapshotInfo, pathPrevSnapshot);
+
+    String[] args = new String[] {
+        "om", "snapshot", "chain",
+        volumeName + "/" + bucketName,
+        snapshotName,
+        "--db", DB_PATH,
+        "--global-previous", globalPrevSnapshotId.toString(),
+        "--path-previous", pathPrevSnapshotId.toString(),
+    };
+
+    setupMockDB(snapshotInfo, iteratorSnapshots);
+
+    CommandLine cli = new OzoneRepair().getCmd();
+    withTextFromSystemIn("y")
+        .execute(() -> cli.execute(args));
+
+    String errorOutput = err.getOutput();
+    assertTrue(errorOutput.contains("globalPreviousSnapshotId: '" + 
globalPrevSnapshotId +
+        "' is equal to given snapshot's ID"));
+  }
+
+  @Test
+  public void testPathPreviousMatchesSnapshotId() throws Exception {
+    String volumeName = "vol1";
+    String bucketName = "bucket1";
+    String snapshotName = "snap1";
+
+    UUID snapshotId = UUID.randomUUID();
+    UUID globalPrevSnapshotId = UUID.randomUUID();
+    // Use same ID for path previous to trigger error
+    UUID pathPrevSnapshotId = snapshotId;
+
+    SnapshotInfo snapshotInfo = SnapshotInfo.newInstance(volumeName, 
bucketName,
+        snapshotName, snapshotId, 0);
+    SnapshotInfo globalPrevSnapshot = SnapshotInfo.newInstance(volumeName, 
bucketName,
+        "global-prev", globalPrevSnapshotId, 0);
+
+    List<SnapshotInfo> iteratorSnapshots = Arrays.asList(
+        snapshotInfo, globalPrevSnapshot);
+
+    String[] args = new String[] {
+        "om", "snapshot", "chain",
+        volumeName + "/" + bucketName,
+        snapshotName,
+        "--db", DB_PATH,
+        "--global-previous", globalPrevSnapshotId.toString(),
+        "--path-previous", pathPrevSnapshotId.toString(),
+    };
+
+    setupMockDB(snapshotInfo, iteratorSnapshots);
+
+    CommandLine cli = new OzoneRepair().getCmd();
+    withTextFromSystemIn("y")
+        .execute(() -> cli.execute(args));
+
+    String errorOutput = err.getOutput();
+    assertTrue(errorOutput.contains("pathPreviousSnapshotId: '" + 
pathPrevSnapshotId +
+        "' is equal to given snapshot's ID"));
+  }
+
+  @Test
+  public void testGlobalPreviousDoesNotExist() throws Exception {
+    String volumeName = "vol1";
+    String bucketName = "bucket1";
+    String snapshotName = "snap1";
+
+    UUID snapshotId = UUID.randomUUID();
+    UUID globalPrevSnapshotId = UUID.randomUUID();
+    UUID pathPrevSnapshotId = UUID.randomUUID();
+
+    SnapshotInfo snapshotInfo = SnapshotInfo.newInstance(volumeName, 
bucketName,
+        snapshotName, snapshotId, 0);
+    SnapshotInfo pathPrevSnapshot = SnapshotInfo.newInstance(volumeName, 
bucketName,
+        "path-prev", pathPrevSnapshotId, 0);
+
+    List<SnapshotInfo> iteratorSnapshots = Arrays.asList(
+        snapshotInfo, pathPrevSnapshot);
+
+    String[] args = new String[] {
+        "om", "snapshot", "chain",
+        volumeName + "/" + bucketName,
+        snapshotName,
+        "--db", DB_PATH,
+        "--global-previous", globalPrevSnapshotId.toString(),
+        "--path-previous", pathPrevSnapshotId.toString(),
+    };
+
+    setupMockDB(snapshotInfo, iteratorSnapshots);
+
+    CommandLine cli = new OzoneRepair().getCmd();
+    withTextFromSystemIn("y")
+        .execute(() -> cli.execute(args));
+
+    String errorOutput = err.getOutput();
+    assertTrue(errorOutput.contains("globalPreviousSnapshotId: '" + 
globalPrevSnapshotId +
+        "' does not exist in snapshotInfoTable"));
+  }
+
+  @Test
+  public void testPathPreviousDoesNotExist() throws Exception {
+    String volumeName = "vol1";
+    String bucketName = "bucket1";
+    String snapshotName = "snap1";
+
+    UUID snapshotId = UUID.randomUUID();
+    UUID globalPrevSnapshotId = UUID.randomUUID();
+    UUID pathPrevSnapshotId = UUID.randomUUID();
+
+    SnapshotInfo snapshotInfo = SnapshotInfo.newInstance(volumeName, 
bucketName,
+        snapshotName, snapshotId, 0);
+    SnapshotInfo globalPrevSnapshot = SnapshotInfo.newInstance(volumeName, 
bucketName,
+        "global-prev", globalPrevSnapshotId, 0);
+
+    List<SnapshotInfo> iteratorSnapshots = Arrays.asList(
+        snapshotInfo, globalPrevSnapshot);
+
+    String[] args = new String[] {
+        "om", "snapshot", "chain",
+        volumeName + "/" + bucketName,
+        snapshotName,
+        "--db", DB_PATH,
+        "--global-previous", globalPrevSnapshotId.toString(),
+        "--path-previous", pathPrevSnapshotId.toString(),
+    };
+
+    setupMockDB(snapshotInfo, iteratorSnapshots);
+
+    CommandLine cli = new OzoneRepair().getCmd();
+    withTextFromSystemIn("y")
+        .execute(() -> cli.execute(args));
+
+    String errorOutput = err.getOutput();
+    assertTrue(errorOutput.contains("pathPreviousSnapshotId: '" + 
pathPrevSnapshotId +
+        "' does not exist in snapshotInfoTable"));
+  }
+}


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

Reply via email to