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 a1345f9fdd HDDS-12368. Seek to correct start key in 
KeyManagerImpl#getTableEntries (#7925)
a1345f9fdd is described below

commit a1345f9fdd43fead7e08e71d6c8b5928d6d82ed2
Author: Swaminathan Balachandran <[email protected]>
AuthorDate: Wed Apr 9 02:10:24 2025 -0700

    HDDS-12368. Seek to correct start key in KeyManagerImpl#getTableEntries 
(#7925)
---
 .../hadoop/hdds/utils/MapBackedTableIterator.java  |  83 ++++++++
 hadoop-ozone/ozone-manager/pom.xml                 |   6 +
 .../org/apache/hadoop/ozone/om/KeyManagerImpl.java |   1 +
 .../apache/hadoop/ozone/om/TestKeyManagerImpl.java | 214 +++++++++++++++++++++
 4 files changed, 304 insertions(+)

diff --git 
a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/utils/MapBackedTableIterator.java
 
b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/utils/MapBackedTableIterator.java
new file mode 100644
index 0000000000..0ca7b45251
--- /dev/null
+++ 
b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/utils/MapBackedTableIterator.java
@@ -0,0 +1,83 @@
+/*
+ * 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.hdds.utils;
+
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.TreeMap;
+import org.apache.hadoop.hdds.utils.db.Table;
+import org.apache.hadoop.hdds.utils.db.TableIterator;
+
+/**
+ * Generic Table Iterator implementation that can be used for unit tests to 
reduce redundant mocking in tests.
+ */
+public class MapBackedTableIterator<V> implements TableIterator<String, 
Table.KeyValue<String, V>> {
+
+  private Iterator<Table.KeyValue<String, V>> itr;
+  private final String prefix;
+  private final TreeMap<String, V> values;
+
+  public MapBackedTableIterator(TreeMap<String, V> values, String prefix) {
+    this.prefix = prefix;
+    this.values = values;
+    this.seekToFirst();
+  }
+
+  @Override
+  public void seekToFirst() {
+    this.itr = this.values.entrySet().stream()
+        .filter(e -> prefix == null || e.getKey().startsWith(prefix))
+        .map(e -> Table.newKeyValue(e.getKey(), e.getValue())).iterator();
+  }
+
+  @Override
+  public void seekToLast() {
+
+  }
+
+  @Override
+  public Table.KeyValue<String, V> seek(String s) throws IOException {
+    this.itr = this.values.entrySet().stream()
+        .filter(e -> prefix == null || e.getKey().startsWith(prefix))
+        .filter(e -> e.getKey().compareTo(s) >= 0)
+        .map(e -> Table.newKeyValue(e.getKey(), e.getValue())).iterator();
+    Map.Entry<String, V> firstEntry = values.ceilingEntry(s);
+    return firstEntry == null ? null : Table.newKeyValue(firstEntry.getKey(), 
firstEntry.getValue());
+  }
+
+  @Override
+  public void removeFromDB() throws IOException {
+
+  }
+
+  @Override
+  public void close() throws IOException {
+
+  }
+
+  @Override
+  public boolean hasNext() {
+    return this.itr.hasNext();
+  }
+
+  @Override
+  public Table.KeyValue<String, V> next() {
+    return itr.next();
+  }
+}
diff --git a/hadoop-ozone/ozone-manager/pom.xml 
b/hadoop-ozone/ozone-manager/pom.xml
index 3389546c9b..cc2aea097b 100644
--- a/hadoop-ozone/ozone-manager/pom.xml
+++ b/hadoop-ozone/ozone-manager/pom.xml
@@ -362,6 +362,12 @@
       <type>test-jar</type>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.apache.ozone</groupId>
+      <artifactId>hdds-server-framework</artifactId>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
     <dependency>
       <groupId>org.apache.ozone</groupId>
       <artifactId>hdds-server-scm</artifactId>
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/KeyManagerImpl.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/KeyManagerImpl.java
index 4357542ff7..f5a984bf40 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/KeyManagerImpl.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/KeyManagerImpl.java
@@ -696,6 +696,7 @@ private <V, R> List<Table.KeyValue<String, R>> 
getTableEntries(String startKey,
     */
     if (startKey != null) {
       tableIterator.seek(startKey);
+    } else {
       tableIterator.seekToFirst();
     }
     int currentCount = 0;
diff --git 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestKeyManagerImpl.java
 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestKeyManagerImpl.java
new file mode 100644
index 0000000000..22740426e2
--- /dev/null
+++ 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestKeyManagerImpl.java
@@ -0,0 +1,214 @@
+/*
+ * 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.om;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.TreeMap;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.hdds.utils.MapBackedTableIterator;
+import org.apache.hadoop.hdds.utils.db.Table;
+import org.apache.hadoop.ozone.om.helpers.OmKeyInfo;
+import org.apache.hadoop.ozone.om.helpers.RepeatedOmKeyInfo;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.Mockito;
+
+/**
+ * Test class for unit tests KeyManagerImpl.
+ */
+public class TestKeyManagerImpl {
+  private static Stream<Arguments> getTableIteratorParameters() {
+    return Stream.of(
+        Arguments.argumentSet("Fetch first 50 entries for volume 0, bucket 0",
+            5, 10, 100, 0, 0, 0, 0, 0, 50, null),
+        Arguments.argumentSet("Fetch first 50 entries for any volume/bucket", 
5, 10, 100, null, null, 0, 0, 0, 50,
+            null),
+        Arguments.argumentSet("Fetch first 30 entries for volume 1, bucket 1", 
5, 10, 100, 1, 1, 0, 0, 0, 30, null),
+        Arguments.argumentSet("Fetch 20 entries from offset (2,2,10) for 
volume 2, bucket 2", 5, 10, 100, 2, 2, 2, 2,
+            10, 20, null),
+        Arguments.argumentSet("Fetch 40 entries from offset (2,2,50) for 
volume 3, bucket 3", 5, 10, 100, 3, 3, 3, 3,
+            50, 40, null),
+        Arguments.argumentSet("Fetch 200 entries from the very beginning (null 
start offsets)", 5, 10, 100, null,
+            null, null, null, null, 200, null),
+        Arguments.argumentSet("Fetch 200 entries starting from bucket 3, key 
50, spanning 3 buckets", 5, 10, 100,
+            null, null, 0, 3, 50, 200, null),
+        Arguments.argumentSet("Invalid: bucket is set but volume is null", 5, 
10, 100, null, 1, 0, 0, 0, 10,
+            IOException.class),
+        Arguments.argumentSet("Invalid: volume is set but bucket is null", 5, 
10, 100, 1, null, 0, 0, 0, 10,
+            IOException.class),
+        Arguments.argumentSet("Fetch 50 entries from volume 2, bucket 5, but 
only 31 exist", 5, 10, 100, 2, 5, 2, 5,
+            70, 50, null),
+        Arguments.argumentSet("Start from last volume (4), second-last bucket 
(8), key 80 but only 131 entries exist",
+            5, 10, 100, null, null, 4, 8, 80, 200, null)
+    );
+  }
+
+  @SuppressWarnings({"checkstyle:ParameterNumber"})
+  private <V> List<Table.KeyValue<String, V>> mockTableIterator(
+      Class<V> valueClass, Table<String, V> table, int numberOfVolumes, int 
numberOfBucketsPerVolume,
+      int numberOfKeysPerBucket, String volumeNamePrefix, String 
bucketNamePrefix, String keyPrefix,
+      Integer volumeNumberFilter, Integer bucketNumberFilter, Integer 
startVolumeNumber, Integer startBucketNumber,
+      Integer startKeyNumber, int numberOfEntries) throws IOException {
+    TreeMap<String, V> values = new TreeMap<>();
+    List<Table.KeyValue<String, V>> keyValues = new ArrayList<>();
+    String startKey = startVolumeNumber == null || startBucketNumber == null 
|| startKeyNumber == null ? null
+        : (String.format("/%s%010d/%s%010d/%s%010d", volumeNamePrefix, 
startVolumeNumber, bucketNamePrefix,
+        startBucketNumber, keyPrefix, startKeyNumber));
+    for (int i = 0; i < numberOfVolumes; i++) {
+      for (int j = 0; j < numberOfBucketsPerVolume; j++) {
+        for (int k = 0; k < numberOfKeysPerBucket; k++) {
+          String key = String.format("/%s%010d/%s%010d/%s%010d", 
volumeNamePrefix, i, bucketNamePrefix, j,
+              keyPrefix, k);
+          V value = valueClass == String.class ? (V) key : 
Mockito.mock(valueClass);
+          values.put(key, value);
+
+          if ((volumeNumberFilter == null || i == volumeNumberFilter) &&
+              (bucketNumberFilter == null || j == bucketNumberFilter) &&
+              (startKey == null || startKey.compareTo(key) <= 0)) {
+            keyValues.add(Table.newKeyValue(key, value));
+          }
+        }
+      }
+    }
+
+    when(table.iterator(anyString())).thenAnswer(i -> new 
MapBackedTableIterator<>(values, i.getArgument(0)));
+    return keyValues.subList(0, Math.min(numberOfEntries, keyValues.size()));
+  }
+
+  @ParameterizedTest
+  @MethodSource("getTableIteratorParameters")
+  @SuppressWarnings({"checkstyle:ParameterNumber"})
+  public void testGetDeletedKeyEntries(int numberOfVolumes, int 
numberOfBucketsPerVolume, int numberOfKeysPerBucket,
+                                       Integer volumeNumber, Integer 
bucketNumber,
+                                       Integer startVolumeNumber, Integer 
startBucketNumber, Integer startKeyNumber,
+                                       int numberOfEntries, Class<? extends 
Exception> expectedException)
+      throws IOException {
+    String volumeNamePrefix = "volume";
+    String bucketNamePrefix = "bucket";
+    String keyPrefix = "key";
+    OzoneConfiguration configuration = new OzoneConfiguration();
+    OMMetadataManager metadataManager = Mockito.mock(OMMetadataManager.class);
+    when(metadataManager.getBucketKeyPrefix(anyString(), 
anyString())).thenAnswer(i ->
+        "/" + i.getArguments()[0] + "/" + i.getArguments()[1] + "/");
+    KeyManagerImpl km = new KeyManagerImpl(null, null, metadataManager, 
configuration, null, null, null);
+    Table<String, RepeatedOmKeyInfo> mockedDeletedTable = 
Mockito.mock(Table.class);
+    when(metadataManager.getDeletedTable()).thenReturn(mockedDeletedTable);
+    List<Table.KeyValue<String, List<OmKeyInfo>>> expectedEntries = 
mockTableIterator(
+        RepeatedOmKeyInfo.class, mockedDeletedTable, numberOfVolumes, 
numberOfBucketsPerVolume, numberOfKeysPerBucket,
+        volumeNamePrefix, bucketNamePrefix, keyPrefix, volumeNumber, 
bucketNumber, startVolumeNumber, startBucketNumber,
+        startKeyNumber, numberOfEntries).stream()
+        .map(kv -> {
+          try {
+            String key = kv.getKey();
+            RepeatedOmKeyInfo value = kv.getValue();
+            List<OmKeyInfo> omKeyInfos = 
Collections.singletonList(Mockito.mock(OmKeyInfo.class));
+            when(value.cloneOmKeyInfoList()).thenReturn(omKeyInfos);
+            return Table.newKeyValue(key, omKeyInfos);
+          } catch (IOException e) {
+            throw new RuntimeException(e);
+          }
+        }).collect(Collectors.toList());
+    String volumeName = volumeNumber == null ? null : 
(String.format("%s%010d", volumeNamePrefix, volumeNumber));
+    String bucketName = bucketNumber == null ? null : 
(String.format("%s%010d", bucketNamePrefix, bucketNumber));
+    String startKey = startVolumeNumber == null || startBucketNumber == null 
|| startKeyNumber == null ? null
+        : (String.format("/%s%010d/%s%010d/%s%010d", volumeNamePrefix, 
startVolumeNumber, bucketNamePrefix,
+        startBucketNumber, keyPrefix, startKeyNumber));
+    if (expectedException != null) {
+      assertThrows(expectedException, () -> 
km.getDeletedKeyEntries(volumeName, bucketName, startKey, numberOfEntries));
+    } else {
+      assertEquals(expectedEntries, km.getDeletedKeyEntries(volumeName, 
bucketName, startKey, numberOfEntries));
+    }
+  }
+
+  @ParameterizedTest
+  @MethodSource("getTableIteratorParameters")
+  @SuppressWarnings({"checkstyle:ParameterNumber"})
+  public void testGetRenameKeyEntries(int numberOfVolumes, int 
numberOfBucketsPerVolume, int numberOfKeysPerBucket,
+                                      Integer volumeNumber, Integer 
bucketNumber,
+                                      Integer startVolumeNumber, Integer 
startBucketNumber, Integer startKeyNumber,
+                                      int numberOfEntries, Class<? extends 
Exception> expectedException)
+      throws IOException {
+    String volumeNamePrefix = "volume";
+    String bucketNamePrefix = "bucket";
+    String keyPrefix = "";
+    OzoneConfiguration configuration = new OzoneConfiguration();
+    OMMetadataManager metadataManager = Mockito.mock(OMMetadataManager.class);
+    when(metadataManager.getBucketKeyPrefix(anyString(), 
anyString())).thenAnswer(i ->
+        "/" + i.getArguments()[0] + "/" + i.getArguments()[1] + "/");
+    KeyManagerImpl km = new KeyManagerImpl(null, null, metadataManager, 
configuration, null, null, null);
+    Table<String, String> mockedRenameTable = Mockito.mock(Table.class);
+    
when(metadataManager.getSnapshotRenamedTable()).thenReturn(mockedRenameTable);
+    List<Table.KeyValue<String, String>> expectedEntries = mockTableIterator(
+        String.class, mockedRenameTable, numberOfVolumes, 
numberOfBucketsPerVolume, numberOfKeysPerBucket,
+        volumeNamePrefix, bucketNamePrefix, keyPrefix, volumeNumber, 
bucketNumber, startVolumeNumber, startBucketNumber,
+        startKeyNumber, numberOfEntries);
+    String volumeName = volumeNumber == null ? null : 
(String.format("%s%010d", volumeNamePrefix, volumeNumber));
+    String bucketName = bucketNumber == null ? null : 
(String.format("%s%010d", bucketNamePrefix, bucketNumber));
+    String startKey = startVolumeNumber == null || startBucketNumber == null 
|| startKeyNumber == null ? null
+        : (String.format("/%s%010d/%s%010d/%s%010d", volumeNamePrefix, 
startVolumeNumber, bucketNamePrefix,
+        startBucketNumber, keyPrefix, startKeyNumber));
+    if (expectedException != null) {
+      assertThrows(expectedException, () -> 
km.getRenamesKeyEntries(volumeName, bucketName, startKey, numberOfEntries));
+    } else {
+      assertEquals(expectedEntries, km.getRenamesKeyEntries(volumeName, 
bucketName, startKey, numberOfEntries));
+    }
+  }
+
+  @ParameterizedTest
+  @MethodSource("getTableIteratorParameters")
+  @SuppressWarnings({"checkstyle:ParameterNumber"})
+  public void testGetDeletedDirEntries(int numberOfVolumes, int 
numberOfBucketsPerVolume, int numberOfKeysPerBucket,
+                                       Integer volumeNumber, Integer 
bucketNumber,
+                                       Integer startVolumeNumber, Integer 
startBucketNumber, Integer startKeyNumber,
+                                       int numberOfEntries, Class<? extends 
Exception> expectedException)
+      throws IOException {
+    String volumeNamePrefix = "";
+    String bucketNamePrefix = "";
+    String keyPrefix = "key";
+    startVolumeNumber = null;
+    OzoneConfiguration configuration = new OzoneConfiguration();
+    OMMetadataManager metadataManager = Mockito.mock(OMMetadataManager.class);
+    when(metadataManager.getBucketKeyPrefixFSO(anyString(), 
anyString())).thenAnswer(i ->
+        "/" + i.getArguments()[0] + "/" + i.getArguments()[1] + "/");
+    KeyManagerImpl km = new KeyManagerImpl(null, null, metadataManager, 
configuration, null, null, null);
+    Table<String, OmKeyInfo> mockedDeletedDirTable = Mockito.mock(Table.class);
+    
when(metadataManager.getDeletedDirTable()).thenReturn(mockedDeletedDirTable);
+    List<Table.KeyValue<String, OmKeyInfo>> expectedEntries = 
mockTableIterator(
+        OmKeyInfo.class, mockedDeletedDirTable, numberOfVolumes, 
numberOfBucketsPerVolume, numberOfKeysPerBucket,
+        volumeNamePrefix, bucketNamePrefix, keyPrefix, volumeNumber, 
bucketNumber, startVolumeNumber, startBucketNumber,
+        startKeyNumber, numberOfEntries);
+    String volumeName = volumeNumber == null ? null : 
(String.format("%s%010d", volumeNamePrefix, volumeNumber));
+    String bucketName = bucketNumber == null ? null : 
(String.format("%s%010d", bucketNamePrefix, bucketNumber));
+    if (expectedException != null) {
+      assertThrows(expectedException, () -> 
km.getDeletedDirEntries(volumeName, bucketName, numberOfEntries));
+    } else {
+      assertEquals(expectedEntries, km.getDeletedDirEntries(volumeName, 
bucketName, numberOfEntries));
+    }
+  }
+}


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

Reply via email to