diff --git a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/utils/db/InMemoryTestTable.java b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/utils/db/InMemoryTestTable.java index 0432c7290635..62e15a08662b 100644 --- a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/utils/db/InMemoryTestTable.java +++ b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/utils/db/InMemoryTestTable.java @@ -19,6 +19,7 @@ import java.io.File; import java.io.IOException; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -28,7 +29,16 @@ * InMemory Table implementation for tests. */ public final class InMemoryTestTable implements Table { - private final Map map = new ConcurrentHashMap<>(); + private final Map map; + + public InMemoryTestTable() { + this(Collections.emptyMap()); + } + + public InMemoryTestTable(Map map) { + this.map = new ConcurrentHashMap<>(); + this.map.putAll(map); + } @Override public void close() { diff --git a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestKeyManagerImpl.java b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestKeyManagerImpl.java index 4af8022bd1db..046ed87abdd9 100644 --- a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestKeyManagerImpl.java +++ b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestKeyManagerImpl.java @@ -53,6 +53,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.Sets; import jakarta.annotation.Nonnull; import java.io.File; @@ -69,6 +70,7 @@ import java.util.Set; import java.util.TreeSet; import java.util.UUID; +import java.util.stream.Collectors; import java.util.stream.Stream; import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.tuple.Pair; @@ -104,6 +106,8 @@ import org.apache.hadoop.hdds.scm.protocol.StorageContainerLocationProtocol; import org.apache.hadoop.hdds.scm.server.SCMConfigurator; import org.apache.hadoop.hdds.scm.server.StorageContainerManager; +import org.apache.hadoop.hdds.utils.db.InMemoryTestTable; +import org.apache.hadoop.hdds.utils.db.Table; import org.apache.hadoop.hdds.utils.db.cache.CacheKey; import org.apache.hadoop.hdds.utils.db.cache.CacheValue; import org.apache.hadoop.ozone.OzoneAcl; @@ -112,6 +116,7 @@ import org.apache.hadoop.ozone.om.exceptions.OMException; import org.apache.hadoop.ozone.om.helpers.BucketLayout; import org.apache.hadoop.ozone.om.helpers.OmBucketInfo; +import org.apache.hadoop.ozone.om.helpers.OmDirectoryInfo; import org.apache.hadoop.ozone.om.helpers.OmKeyArgs; import org.apache.hadoop.ozone.om.helpers.OmKeyInfo; import org.apache.hadoop.ozone.om.helpers.OmKeyLocationInfo; @@ -1585,6 +1590,91 @@ void testGetNotExistedPart() throws IOException { assertEquals(0, locationList.size()); } + private OmKeyInfo getMockedOmKeyInfo(OmBucketInfo bucketInfo, long parentId, String key, long objectId) { + OmKeyInfo omKeyInfo = mock(OmKeyInfo.class); + if (bucketInfo.getBucketLayout().isFileSystemOptimized()) { + when(omKeyInfo.getFileName()).thenReturn(key); + when(omKeyInfo.getParentObjectID()).thenReturn(parentId); + } else { + when(omKeyInfo.getKeyName()).thenReturn(key); + } + when(omKeyInfo.getObjectID()).thenReturn(objectId); + return omKeyInfo; + } + + private OmDirectoryInfo getMockedOmDirInfo(long parentId, String key, long objectId) { + OmDirectoryInfo omKeyInfo = mock(OmDirectoryInfo.class); + when(omKeyInfo.getName()).thenReturn(key); + when(omKeyInfo.getParentObjectID()).thenReturn(parentId); + when(omKeyInfo.getObjectID()).thenReturn(objectId); + return omKeyInfo; + } + + private String getDirectoryKey(long volumeId, OmBucketInfo bucketInfo, OmKeyInfo omKeyInfo) { + if (bucketInfo.getBucketLayout().isFileSystemOptimized()) { + return volumeId + "/" + bucketInfo.getObjectID() + "/" + omKeyInfo.getParentObjectID() + "/" + + omKeyInfo.getFileName(); + } else { + return bucketInfo.getVolumeName() + "/" + bucketInfo.getBucketName() + "/" + omKeyInfo.getKeyName(); + } + } + + private String getDirectoryKey(long volumeId, OmBucketInfo bucketInfo, OmDirectoryInfo omDirInfo) { + return volumeId + "/" + bucketInfo.getObjectID() + "/" + omDirInfo.getParentObjectID() + "/" + + omDirInfo.getName(); + } + + private String getRenameKey(String volume, String bucket, long objectId) { + return volume + "/" + bucket + "/" + objectId; + } + + @Test + public void testPreviousSnapshotOzoneDirInfo() throws IOException { + OMMetadataManager omMetadataManager = mock(OMMetadataManager.class); + when(omMetadataManager.getOzonePathKey(anyLong(), anyLong(), anyLong(), anyString())) + .thenAnswer(i -> Arrays.stream(i.getArguments()).map(Object::toString) + .collect(Collectors.joining("/"))); + when(omMetadataManager.getRenameKey(anyString(), anyString(), anyLong())).thenAnswer( + i -> getRenameKey(i.getArgument(0), i.getArgument(1), i.getArgument(2))); + + OMMetadataManager previousMetadataManager = mock(OMMetadataManager.class); + OzoneConfiguration configuration = new OzoneConfiguration(); + KeyManagerImpl km = new KeyManagerImpl(null, null, omMetadataManager, configuration, null, null, null); + KeyManagerImpl prevKM = new KeyManagerImpl(null, null, previousMetadataManager, configuration, null, null, null); + long volumeId = 1L; + OmBucketInfo bucketInfo = OmBucketInfo.newBuilder().setBucketName(BUCKET_NAME).setVolumeName(VOLUME_NAME) + .setObjectID(2L).setBucketLayout(BucketLayout.FILE_SYSTEM_OPTIMIZED).build(); + OmDirectoryInfo prevKey = getMockedOmDirInfo(5, "key", 1); + OmDirectoryInfo prevKey2 = getMockedOmDirInfo(7, "key2", 2); + OmKeyInfo currentKey = getMockedOmKeyInfo(bucketInfo, 6, "renamedKey", 1); + OmDirectoryInfo currentKeyDir = getMockedOmDirInfo(6, "renamedKey", 1); + OmKeyInfo currentKey2 = getMockedOmKeyInfo(bucketInfo, 7, "key2", 2); + OmDirectoryInfo currentKeyDir2 = getMockedOmDirInfo(7, "key2", 2); + OmKeyInfo currentKey3 = getMockedOmKeyInfo(bucketInfo, 8, "key3", 3); + OmDirectoryInfo currentKeyDir3 = getMockedOmDirInfo(8, "key3", 3); + OmKeyInfo currentKey4 = getMockedOmKeyInfo(bucketInfo, 8, "key4", 4); + OmDirectoryInfo currentKeyDir4 = getMockedOmDirInfo(8, "key4", 4); + Table prevDirTable = new InMemoryTestTable<>( + ImmutableMap.of(getDirectoryKey(volumeId, bucketInfo, prevKey), prevKey, + getDirectoryKey(volumeId, bucketInfo, prevKey2), prevKey2)); + Table renameTable = new InMemoryTestTable<>( + ImmutableMap.of(getRenameKey(VOLUME_NAME, BUCKET_NAME, 1), + getDirectoryKey(volumeId, bucketInfo, prevKey), + getRenameKey(VOLUME_NAME, BUCKET_NAME, 3), getDirectoryKey(volumeId, bucketInfo, + getMockedOmKeyInfo(bucketInfo, 6, "unknownKey", 9)))); + when(previousMetadataManager.getDirectoryTable()).thenReturn(prevDirTable); + when(omMetadataManager.getSnapshotRenamedTable()).thenReturn(renameTable); + assertEquals(prevKey, km.getPreviousSnapshotOzoneDirInfo(volumeId, bucketInfo, currentKey).apply(prevKM)); + assertEquals(prevKey2, km.getPreviousSnapshotOzoneDirInfo(volumeId, bucketInfo, currentKey2).apply(prevKM)); + assertNull(km.getPreviousSnapshotOzoneDirInfo(volumeId, bucketInfo, currentKey3).apply(prevKM)); + assertNull(km.getPreviousSnapshotOzoneDirInfo(volumeId, bucketInfo, currentKey4).apply(prevKM)); + + assertEquals(prevKey, km.getPreviousSnapshotOzoneDirInfo(volumeId, bucketInfo, currentKeyDir).apply(prevKM)); + assertEquals(prevKey2, km.getPreviousSnapshotOzoneDirInfo(volumeId, bucketInfo, currentKeyDir2).apply(prevKM)); + assertNull(km.getPreviousSnapshotOzoneDirInfo(volumeId, bucketInfo, currentKeyDir3).apply(prevKM)); + assertNull(km.getPreviousSnapshotOzoneDirInfo(volumeId, bucketInfo, currentKeyDir4).apply(prevKM)); + } + private void initKeyTableForMultipartTest(String keyName, String volume) throws IOException { List locationInfoGroups = new ArrayList<>(); List locationInfoList = new ArrayList<>(); diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/KeyManager.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/KeyManager.java index a0045e175e31..268db5b75188 100644 --- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/KeyManager.java +++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/KeyManager.java @@ -31,6 +31,8 @@ import org.apache.hadoop.ozone.om.fs.OzoneManagerFS; import org.apache.hadoop.ozone.om.helpers.BucketLayout; import org.apache.hadoop.ozone.om.helpers.ListKeysResult; +import org.apache.hadoop.ozone.om.helpers.OmBucketInfo; +import org.apache.hadoop.ozone.om.helpers.OmDirectoryInfo; import org.apache.hadoop.ozone.om.helpers.OmKeyArgs; import org.apache.hadoop.ozone.om.helpers.OmKeyInfo; import org.apache.hadoop.ozone.om.helpers.OmMultipartUploadList; @@ -41,6 +43,7 @@ import org.apache.hadoop.ozone.om.service.SnapshotDeletingService; import org.apache.hadoop.ozone.om.service.SnapshotDirectoryCleaningService; import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.ExpiredMultipartUploadsBucket; +import org.apache.ratis.util.function.CheckedFunction; /** * Handles key level commands. @@ -84,7 +87,6 @@ OmKeyInfo lookupKey(OmKeyArgs args, ResolvedBucket bucketLayout, OmKeyInfo getKeyInfo(OmKeyArgs args, ResolvedBucket buctket, String clientAddress) throws IOException; - /** * Returns a list of keys represented by {@link OmKeyInfo} * in the given bucket. @@ -135,6 +137,18 @@ List> getRenamesKeyEntries( String volume, String bucket, String startKey, int size) throws IOException; + /** + * Returns the previous snapshot's ozone directorInfo corresponding for the object. + */ + CheckedFunction getPreviousSnapshotOzoneDirInfo( + long volumeId, OmBucketInfo bucketInfo, OmDirectoryInfo directoryInfo) throws IOException; + + /** + * Returns the previous snapshot's ozone directoryInfo corresponding for the object. + */ + CheckedFunction getPreviousSnapshotOzoneDirInfo( + long volumeId, OmBucketInfo bucketInfo, OmKeyInfo directoryInfo) throws IOException; + /** * Returns a list deleted entries from the deletedTable. * 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 ec448dabf239..9ec658db1a01 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 @@ -174,6 +174,7 @@ import org.apache.hadoop.security.SecurityUtil; import org.apache.hadoop.util.ReflectionUtils; import org.apache.hadoop.util.Time; +import org.apache.ratis.util.function.CheckedFunction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -774,6 +775,33 @@ public List> getRenamesKeyEntries( } } + @Override + public CheckedFunction getPreviousSnapshotOzoneDirInfo( + long volumeId, OmBucketInfo bucketInfo, OmDirectoryInfo keyInfo) throws IOException { + String currentKeyPath = metadataManager.getOzonePathKey(volumeId, bucketInfo.getObjectID(), + keyInfo.getParentObjectID(), keyInfo.getName()); + return getPreviousSnapshotOzonePathInfo(bucketInfo, keyInfo.getObjectID(), currentKeyPath, + (km) -> km.getMetadataManager().getDirectoryTable()); + } + + @Override + public CheckedFunction getPreviousSnapshotOzoneDirInfo( + long volumeId, OmBucketInfo bucketInfo, OmKeyInfo keyInfo) throws IOException { + String currentKeyPath = metadataManager.getOzonePathKey(volumeId, bucketInfo.getObjectID(), + keyInfo.getParentObjectID(), keyInfo.getFileName()); + return getPreviousSnapshotOzonePathInfo(bucketInfo, keyInfo.getObjectID(), currentKeyPath, + (previousSnapshotKM) -> previousSnapshotKM.getMetadataManager().getDirectoryTable()); + } + + private CheckedFunction getPreviousSnapshotOzonePathInfo( + OmBucketInfo bucketInfo, long objectId, String currentKeyPath, + Function> table) throws IOException { + String renameKey = metadataManager.getRenameKey(bucketInfo.getVolumeName(), bucketInfo.getBucketName(), objectId); + String renamedKey = metadataManager.getSnapshotRenamedTable().getIfExist(renameKey); + return (previousSnapshotKM) -> table.apply(previousSnapshotKM).get( + renamedKey != null ? renamedKey : currentKeyPath); + } + @Override public List>> getDeletedKeyEntries( String volume, String bucket, String startKey, int size) throws IOException { diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/snapshot/filter/ReclaimableDirFilter.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/snapshot/filter/ReclaimableDirFilter.java new file mode 100644 index 000000000000..7384ed88a9b1 --- /dev/null +++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/snapshot/filter/ReclaimableDirFilter.java @@ -0,0 +1,73 @@ +/* + * 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.snapshot.filter; + +import java.io.IOException; +import org.apache.hadoop.hdds.utils.db.Table; +import org.apache.hadoop.ozone.om.KeyManager; +import org.apache.hadoop.ozone.om.OmSnapshot; +import org.apache.hadoop.ozone.om.OmSnapshotManager; +import org.apache.hadoop.ozone.om.OzoneManager; +import org.apache.hadoop.ozone.om.SnapshotChainManager; +import org.apache.hadoop.ozone.om.helpers.OmBucketInfo; +import org.apache.hadoop.ozone.om.helpers.OmDirectoryInfo; +import org.apache.hadoop.ozone.om.helpers.OmKeyInfo; +import org.apache.hadoop.ozone.om.helpers.SnapshotInfo; +import org.apache.hadoop.ozone.om.lock.IOzoneManagerLock; +import org.apache.hadoop.ozone.om.snapshot.ReferenceCounted; + +/** + * Class to filter out deleted directories which are reclaimable based on their presence in previous snapshot in + * the snapshot chain. + */ +public class ReclaimableDirFilter extends ReclaimableFilter { + + public ReclaimableDirFilter(OzoneManager ozoneManager, + OmSnapshotManager omSnapshotManager, SnapshotChainManager snapshotChainManager, + SnapshotInfo currentSnapshotInfo, KeyManager keyManager, + IOzoneManagerLock lock) { + super(ozoneManager, omSnapshotManager, snapshotChainManager, currentSnapshotInfo, keyManager, lock, 1); + } + + @Override + protected String getVolumeName(Table.KeyValue keyValue) throws IOException { + return keyValue.getValue().getVolumeName(); + } + + @Override + protected String getBucketName(Table.KeyValue keyValue) throws IOException { + return keyValue.getValue().getBucketName(); + } + + @Override + protected Boolean isReclaimable(Table.KeyValue deletedDirInfo) throws IOException { + ReferenceCounted previousSnapshot = getPreviousOmSnapshot(0); + KeyManager prevKeyManager = previousSnapshot == null ? null : previousSnapshot.get().getKeyManager(); + return isDirReclaimable(getVolumeId(), getBucketInfo(), deletedDirInfo.getValue(), getKeyManager(), prevKeyManager); + } + + private boolean isDirReclaimable(long volumeId, OmBucketInfo bucketInfo, OmKeyInfo dirInfo, + KeyManager keyManager, KeyManager previousKeyManager) throws IOException { + if (previousKeyManager == null) { + return true; + } + OmDirectoryInfo prevDirectoryInfo = + keyManager.getPreviousSnapshotOzoneDirInfo(volumeId, bucketInfo, dirInfo).apply(previousKeyManager); + return prevDirectoryInfo == null || prevDirectoryInfo.getObjectID() != dirInfo.getObjectID(); + } +} diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/snapshot/filter/ReclaimableRenameEntryFilter.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/snapshot/filter/ReclaimableRenameEntryFilter.java index 3751855e9006..563f71d81f4c 100644 --- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/snapshot/filter/ReclaimableRenameEntryFilter.java +++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/snapshot/filter/ReclaimableRenameEntryFilter.java @@ -32,7 +32,7 @@ import org.apache.hadoop.ozone.om.snapshot.ReferenceCounted; /** - * Filter to return rename table entries which are reclaimable based on the key presence in previous snapshot's + * Class to filter out rename table entries which are reclaimable based on the key presence in previous snapshot's * keyTable/DirectoryTable in the snapshot chain. */ public class ReclaimableRenameEntryFilter extends ReclaimableFilter { diff --git a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/snapshot/filter/TestReclaimableDirFilter.java b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/snapshot/filter/TestReclaimableDirFilter.java new file mode 100644 index 000000000000..a8c6a11b2eca --- /dev/null +++ b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/snapshot/filter/TestReclaimableDirFilter.java @@ -0,0 +1,143 @@ +/* + * 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.snapshot.filter; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import org.apache.hadoop.hdds.utils.db.Table; +import org.apache.hadoop.ozone.om.KeyManager; +import org.apache.hadoop.ozone.om.OmSnapshot; +import org.apache.hadoop.ozone.om.OmSnapshotManager; +import org.apache.hadoop.ozone.om.OzoneManager; +import org.apache.hadoop.ozone.om.SnapshotChainManager; +import org.apache.hadoop.ozone.om.helpers.OmBucketInfo; +import org.apache.hadoop.ozone.om.helpers.OmDirectoryInfo; +import org.apache.hadoop.ozone.om.helpers.OmKeyInfo; +import org.apache.hadoop.ozone.om.helpers.SnapshotInfo; +import org.apache.hadoop.ozone.om.lock.IOzoneManagerLock; +import org.apache.hadoop.ozone.om.snapshot.ReferenceCounted; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.rocksdb.RocksDBException; + +/** + * Test class for ReclaimableDirFilter. + */ +public class TestReclaimableDirFilter extends AbstractReclaimableFilterTest { + @Override + protected ReclaimableFilter initializeFilter(OzoneManager om, OmSnapshotManager snapshotManager, + SnapshotChainManager chainManager, SnapshotInfo currentSnapshotInfo, + KeyManager km, IOzoneManagerLock lock, + int numberOfPreviousSnapshotsFromChain) { + return new ReclaimableDirFilter(om, snapshotManager, chainManager, currentSnapshotInfo, km, lock); + } + + List testReclaimableFilterArguments() { + List arguments = new ArrayList<>(); + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 5; j++) { + arguments.add(Arguments.of(i, j)); + } + } + return arguments; + } + + private void testReclaimableDirFilter(String volume, String bucket, int index, + OmKeyInfo dirInfo, OmDirectoryInfo prevDirInfo, + Boolean expectedValue) + throws IOException { + List snapshotInfos = getLastSnapshotInfos(volume, bucket, 1, index); + assertEquals(snapshotInfos.size(), 1); + SnapshotInfo prevSnapshotInfo = snapshotInfos.get(0); + OmBucketInfo bucketInfo = getOzoneManager().getBucketInfo(volume, bucket); + long volumeId = getOzoneManager().getMetadataManager().getVolumeId(volume); + KeyManager keyManager = getKeyManager(); + if (prevSnapshotInfo != null) { + ReferenceCounted prevSnap = Optional.ofNullable(prevSnapshotInfo) + .map(info -> { + try { + return getOmSnapshotManager().getActiveSnapshot(volume, bucket, info.getName()); + } catch (IOException e) { + throw new RuntimeException(e); + } + }).orElse(null); + mockOmSnapshot(prevSnap); + when(keyManager.getPreviousSnapshotOzoneDirInfo(eq(volumeId), eq(bucketInfo), eq(dirInfo))) + .thenReturn((km) -> prevDirInfo); + } + + when(dirInfo.getVolumeName()).thenReturn(volume); + when(dirInfo.getBucketName()).thenReturn(bucket); + assertEquals(expectedValue, getReclaimableFilter().apply(Table.newKeyValue("key", dirInfo))); + } + + private OmKeyInfo getMockedOmKeyInfo(long objectId) { + OmKeyInfo keyInfo = mock(OmKeyInfo.class); + when(keyInfo.getObjectID()).thenReturn(objectId); + return keyInfo; + } + + private OmDirectoryInfo getMockedOmDirInfo(long objectId) { + OmDirectoryInfo keyInfo = mock(OmDirectoryInfo.class); + when(keyInfo.getObjectID()).thenReturn(objectId); + return keyInfo; + } + + private KeyManager mockOmSnapshot(ReferenceCounted snapshot) { + if (snapshot != null) { + OmSnapshot omSnapshot = snapshot.get(); + KeyManager keyManager = mock(KeyManager.class); + when(omSnapshot.getKeyManager()).thenReturn(keyManager); + return keyManager; + } + return null; + } + + @ParameterizedTest + @MethodSource("testReclaimableFilterArguments") + public void testNonReclaimableDirectory(int actualNumberOfSnapshots, int index) throws IOException, RocksDBException { + setup(1, actualNumberOfSnapshots, index, 4, 2); + String volume = getVolumes().get(3); + String bucket = getBuckets().get(1); + index = Math.min(index, actualNumberOfSnapshots); + OmKeyInfo dirInfo = getMockedOmKeyInfo(1); + OmDirectoryInfo prevDirectoryInfo = index - 1 >= 0 ? getMockedOmDirInfo(1) : null; + testReclaimableDirFilter(volume, bucket, index, dirInfo, prevDirectoryInfo, prevDirectoryInfo == null); + } + + @ParameterizedTest + @MethodSource("testReclaimableFilterArguments") + public void testReclaimableDirectoryWithDifferentObjId(int actualNumberOfSnapshots, int index) + throws IOException, RocksDBException { + setup(1, actualNumberOfSnapshots, index, 4, 2); + String volume = getVolumes().get(3); + String bucket = getBuckets().get(1); + index = Math.min(index, actualNumberOfSnapshots); + OmKeyInfo dirInfo = getMockedOmKeyInfo(1); + OmDirectoryInfo prevDirectoryInfo = index - 1 >= 0 ? getMockedOmDirInfo(2) : null; + testReclaimableDirFilter(volume, bucket, index, dirInfo, prevDirectoryInfo, true); + } +}