Skip to content

Commit d3faab3

Browse files
authored
HDDS-12561. Reclaimable Rename entry filter for reclaiming renaming entries (apache#8054)
1 parent af1f98c commit d3faab3

File tree

4 files changed

+308
-0
lines changed

4 files changed

+308
-0
lines changed

hadoop-ozone/interface-storage/src/main/java/org/apache/hadoop/ozone/om/OMMetadataManager.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,11 @@ default String getOpenFileName(long volumeId, long bucketId, long parentObjectId
613613
*/
614614
String getRenameKey(String volume, String bucket, long objectID);
615615

616+
/**
617+
* Given renameKey, return the volume, bucket and objectID from the key.
618+
*/
619+
String[] splitRenameKey(String renameKey);
620+
616621
/**
617622
* Returns the DB key name of a multipart upload key in OM metadata store
618623
* for FSO-enabled buckets.

hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmMetadataManagerImpl.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1882,6 +1882,12 @@ public String getRenameKey(String volumeName, String bucketName,
18821882
return renameKey.toString();
18831883
}
18841884

1885+
@Override
1886+
public String[] splitRenameKey(String renameKey) {
1887+
String[] splitVals = renameKey.split(OM_KEY_PREFIX);
1888+
return new String[]{splitVals[1], splitVals[2], splitVals[3]};
1889+
}
1890+
18851891
@Override
18861892
public String getMultipartKey(long volumeId, long bucketId,
18871893
long parentID, String fileName,
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.hadoop.ozone.om.snapshot.filter;
19+
20+
import java.io.IOException;
21+
import org.apache.hadoop.hdds.utils.db.Table;
22+
import org.apache.hadoop.ozone.om.KeyManager;
23+
import org.apache.hadoop.ozone.om.OmSnapshot;
24+
import org.apache.hadoop.ozone.om.OmSnapshotManager;
25+
import org.apache.hadoop.ozone.om.OzoneManager;
26+
import org.apache.hadoop.ozone.om.SnapshotChainManager;
27+
import org.apache.hadoop.ozone.om.helpers.OmDirectoryInfo;
28+
import org.apache.hadoop.ozone.om.helpers.OmKeyInfo;
29+
import org.apache.hadoop.ozone.om.helpers.SnapshotInfo;
30+
import org.apache.hadoop.ozone.om.helpers.WithObjectID;
31+
import org.apache.hadoop.ozone.om.lock.IOzoneManagerLock;
32+
import org.apache.hadoop.ozone.om.snapshot.ReferenceCounted;
33+
34+
/**
35+
* Filter to return rename table entries which are reclaimable based on the key presence in previous snapshot's
36+
* keyTable/DirectoryTable in the snapshot chain.
37+
*/
38+
public class ReclaimableRenameEntryFilter extends ReclaimableFilter<String> {
39+
40+
public ReclaimableRenameEntryFilter(OzoneManager ozoneManager,
41+
OmSnapshotManager omSnapshotManager, SnapshotChainManager snapshotChainManager,
42+
SnapshotInfo currentSnapshotInfo, KeyManager keyManager,
43+
IOzoneManagerLock lock) {
44+
super(ozoneManager, omSnapshotManager, snapshotChainManager, currentSnapshotInfo, keyManager, lock, 1);
45+
}
46+
47+
/**
48+
* Function which checks whether the objectId corresponding to the rename entry exists in the previous snapshot. If
49+
* the entry doesn't exist in the previous keyTable/directoryTable then the rename entry can be deleted since there
50+
* is no reference for this rename entry.
51+
* @return true if there is no reference for the objectId based on the rename entry otherwise false.
52+
* @throws IOException
53+
*/
54+
@Override
55+
protected Boolean isReclaimable(Table.KeyValue<String, String> renameEntry) throws IOException {
56+
ReferenceCounted<OmSnapshot> previousSnapshot = getPreviousOmSnapshot(0);
57+
Table<String, OmKeyInfo> previousKeyTable = null;
58+
Table<String, OmDirectoryInfo> prevDirTable = null;
59+
if (previousSnapshot != null) {
60+
previousKeyTable = previousSnapshot.get().getMetadataManager().getKeyTable(getBucketInfo().getBucketLayout());
61+
if (getBucketInfo().getBucketLayout().isFileSystemOptimized()) {
62+
prevDirTable = previousSnapshot.get().getMetadataManager().getDirectoryTable();
63+
}
64+
}
65+
return isRenameEntryReclaimable(renameEntry, prevDirTable, previousKeyTable);
66+
}
67+
68+
@Override
69+
protected String getVolumeName(Table.KeyValue<String, String> keyValue) throws IOException {
70+
return getKeyManager().getMetadataManager().splitRenameKey(keyValue.getKey())[0];
71+
}
72+
73+
@Override
74+
protected String getBucketName(Table.KeyValue<String, String> keyValue) throws IOException {
75+
return getKeyManager().getMetadataManager().splitRenameKey(keyValue.getKey())[1];
76+
}
77+
78+
@SafeVarargs
79+
private final boolean isRenameEntryReclaimable(Table.KeyValue<String, String> renameEntry,
80+
Table<String, ? extends WithObjectID>... previousTables)
81+
throws IOException {
82+
for (Table<String, ? extends WithObjectID> previousTable : previousTables) {
83+
if (previousTable != null) {
84+
String prevDbKey = renameEntry.getValue();
85+
WithObjectID withObjectID = previousTable.getIfExist(prevDbKey);
86+
if (withObjectID != null) {
87+
return false;
88+
}
89+
}
90+
}
91+
return true;
92+
}
93+
}
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.hadoop.ozone.om.snapshot.filter;
19+
20+
import static org.junit.jupiter.api.Assertions.assertEquals;
21+
import static org.mockito.ArgumentMatchers.anyString;
22+
import static org.mockito.ArgumentMatchers.eq;
23+
import static org.mockito.Mockito.mock;
24+
import static org.mockito.Mockito.when;
25+
26+
import com.google.common.collect.ImmutableMap;
27+
import java.io.IOException;
28+
import java.util.ArrayList;
29+
import java.util.Collections;
30+
import java.util.List;
31+
import java.util.Map;
32+
import java.util.Optional;
33+
import java.util.UUID;
34+
import org.apache.hadoop.hdds.utils.db.Table;
35+
import org.apache.hadoop.ozone.om.KeyManager;
36+
import org.apache.hadoop.ozone.om.OMMetadataManager;
37+
import org.apache.hadoop.ozone.om.OmSnapshot;
38+
import org.apache.hadoop.ozone.om.OmSnapshotManager;
39+
import org.apache.hadoop.ozone.om.OzoneManager;
40+
import org.apache.hadoop.ozone.om.SnapshotChainManager;
41+
import org.apache.hadoop.ozone.om.helpers.BucketLayout;
42+
import org.apache.hadoop.ozone.om.helpers.OmBucketInfo;
43+
import org.apache.hadoop.ozone.om.helpers.OmDirectoryInfo;
44+
import org.apache.hadoop.ozone.om.helpers.OmKeyInfo;
45+
import org.apache.hadoop.ozone.om.helpers.SnapshotInfo;
46+
import org.apache.hadoop.ozone.om.lock.IOzoneManagerLock;
47+
import org.apache.hadoop.ozone.om.snapshot.ReferenceCounted;
48+
import org.junit.jupiter.params.ParameterizedTest;
49+
import org.junit.jupiter.params.provider.Arguments;
50+
import org.junit.jupiter.params.provider.MethodSource;
51+
import org.rocksdb.RocksDBException;
52+
53+
/**
54+
* Test class for ReclaimableRenameEntryFilter.
55+
*/
56+
public class TestReclaimableRenameEntryFilter extends AbstractReclaimableFilterTest {
57+
@Override
58+
protected ReclaimableFilter initializeFilter(OzoneManager om, OmSnapshotManager snapshotManager,
59+
SnapshotChainManager chainManager, SnapshotInfo currentSnapshotInfo,
60+
KeyManager km, IOzoneManagerLock lock,
61+
int numberOfPreviousSnapshotsFromChain) {
62+
return new ReclaimableRenameEntryFilter(om, snapshotManager, chainManager, currentSnapshotInfo, km, lock);
63+
}
64+
65+
List<Arguments> testReclaimableFilterArguments() {
66+
List<Arguments> arguments = new ArrayList<>();
67+
for (int i = 0; i < 3; i++) {
68+
for (int j = 0; j < 5; j++) {
69+
arguments.add(Arguments.of(i, j));
70+
}
71+
}
72+
return arguments;
73+
}
74+
75+
private void testReclaimableRenameEntryFilter(String volume, String bucket, int index,
76+
String value,
77+
Table<String, OmKeyInfo> keyTable,
78+
Table<String, OmDirectoryInfo> dirTable,
79+
Boolean expectedValue)
80+
throws IOException {
81+
List<SnapshotInfo> snapshotInfos = getLastSnapshotInfos(volume, bucket, 1, index);
82+
SnapshotInfo prevSnapshotInfo = snapshotInfos.get(0);
83+
OmBucketInfo bucketInfo = getOzoneManager().getBucketInfo(volume, bucket);
84+
if (prevSnapshotInfo != null) {
85+
ReferenceCounted<OmSnapshot> prevSnap = Optional.ofNullable(prevSnapshotInfo)
86+
.map(info -> {
87+
try {
88+
return getOmSnapshotManager().getActiveSnapshot(volume, bucket, info.getName());
89+
} catch (IOException e) {
90+
throw new RuntimeException(e);
91+
}
92+
}).orElse(null);
93+
mockOmSnapshot(prevSnap, bucketInfo, keyTable, dirTable);
94+
}
95+
String key = bucketInfo.getVolumeName() + "/" + bucketInfo.getBucketName() + "/" + 1;
96+
String[] keySplit = key.split("/");
97+
KeyManager km = getKeyManager();
98+
OMMetadataManager omMetadataManager = mock(OMMetadataManager.class);
99+
when(km.getMetadataManager()).thenReturn(omMetadataManager);
100+
when(omMetadataManager.splitRenameKey(eq(key))).thenReturn(keySplit);
101+
assertEquals(expectedValue, getReclaimableFilter().apply(Table.newKeyValue(key, value)));
102+
}
103+
104+
private <T> Table<String, T> getMockedTable(Map<String, T> map) throws IOException {
105+
Table<String, T> table = mock(Table.class);
106+
when(table.get(anyString())).thenAnswer(i -> map.get(i.getArgument(0)));
107+
when(table.getIfExist(anyString())).thenAnswer(i -> map.get(i.getArgument(0)));
108+
return table;
109+
}
110+
111+
private <T> Table<String, T> getFailingMockedTable() throws IOException {
112+
Table<String, T> table = mock(Table.class);
113+
when(table.get(anyString())).thenThrow(new IOException());
114+
when(table.getIfExist(anyString())).thenThrow(new IOException());
115+
return table;
116+
}
117+
118+
private void mockOmSnapshot(ReferenceCounted<OmSnapshot> snapshot,
119+
OmBucketInfo bucketInfo, Table<String, OmKeyInfo> keyTable,
120+
Table<String, OmDirectoryInfo> dirTable) {
121+
if (snapshot != null) {
122+
OmSnapshot omSnapshot = snapshot.get();
123+
OMMetadataManager omMetadataManager = mock(OMMetadataManager.class);
124+
when(omSnapshot.getMetadataManager()).thenReturn(omMetadataManager);
125+
when(omMetadataManager.getKeyTable(eq(bucketInfo.getBucketLayout()))).thenReturn(keyTable);
126+
when(omMetadataManager.getDirectoryTable()).thenReturn(dirTable);
127+
}
128+
}
129+
130+
@ParameterizedTest
131+
@MethodSource("testReclaimableFilterArguments")
132+
public void testNonReclaimableRenameEntryWithKeyNonFSO(int actualNumberOfSnapshots, int index)
133+
throws IOException, RocksDBException {
134+
setup(1, actualNumberOfSnapshots, index, 4, 2,
135+
BucketLayout.OBJECT_STORE);
136+
String volume = getVolumes().get(3);
137+
String bucket = getBuckets().get(1);
138+
index = Math.min(index, actualNumberOfSnapshots);
139+
String value = UUID.randomUUID().toString();
140+
Table<String, OmKeyInfo> keyTable = getMockedTable(ImmutableMap.of(value, mock(OmKeyInfo.class)));
141+
Table<String, OmDirectoryInfo> directoryTable = getFailingMockedTable();
142+
testReclaimableRenameEntryFilter(volume, bucket, index, value, keyTable, directoryTable, index == 0);
143+
}
144+
145+
@ParameterizedTest
146+
@MethodSource("testReclaimableFilterArguments")
147+
public void testReclaimableRenameEntryWithKeyNonFSO(int actualNumberOfSnapshots, int index)
148+
throws IOException, RocksDBException {
149+
setup(1, actualNumberOfSnapshots, index, 4, 2,
150+
BucketLayout.OBJECT_STORE);
151+
String volume = getVolumes().get(3);
152+
String bucket = getBuckets().get(1);
153+
index = Math.min(index, actualNumberOfSnapshots);
154+
String value = UUID.randomUUID().toString();
155+
Table<String, OmKeyInfo> keyTable = getMockedTable(Collections.emptyMap());
156+
Table<String, OmDirectoryInfo> directoryTable = getFailingMockedTable();
157+
testReclaimableRenameEntryFilter(volume, bucket, index, value, keyTable, directoryTable, true);
158+
}
159+
160+
@ParameterizedTest
161+
@MethodSource("testReclaimableFilterArguments")
162+
public void testReclaimableRenameEntryWithFSO(int actualNumberOfSnapshots, int index)
163+
throws IOException, RocksDBException {
164+
setup(1, actualNumberOfSnapshots, index, 4, 2,
165+
BucketLayout.FILE_SYSTEM_OPTIMIZED);
166+
String volume = getVolumes().get(3);
167+
String bucket = getBuckets().get(1);
168+
index = Math.min(index, actualNumberOfSnapshots);
169+
String value = UUID.randomUUID().toString();
170+
Table<String, OmKeyInfo> keyTable = getMockedTable(Collections.emptyMap());
171+
Table<String, OmDirectoryInfo> directoryTable = getMockedTable(Collections.emptyMap());
172+
testReclaimableRenameEntryFilter(volume, bucket, index, value, keyTable, directoryTable, true);
173+
}
174+
175+
@ParameterizedTest
176+
@MethodSource("testReclaimableFilterArguments")
177+
public void testNonReclaimableRenameEntryWithFileFSO(int actualNumberOfSnapshots, int index)
178+
throws IOException, RocksDBException {
179+
setup(1, actualNumberOfSnapshots, index, 4, 2,
180+
BucketLayout.FILE_SYSTEM_OPTIMIZED);
181+
String volume = getVolumes().get(3);
182+
String bucket = getBuckets().get(1);
183+
index = Math.min(index, actualNumberOfSnapshots);
184+
String value = UUID.randomUUID().toString();
185+
Table<String, OmKeyInfo> keyTable = getMockedTable(ImmutableMap.of(value, mock(OmKeyInfo.class)));
186+
Table<String, OmDirectoryInfo> directoryTable = getMockedTable(Collections.emptyMap());
187+
testReclaimableRenameEntryFilter(volume, bucket, index, value, keyTable, directoryTable, index == 0);
188+
}
189+
190+
@ParameterizedTest
191+
@MethodSource("testReclaimableFilterArguments")
192+
public void testNonReclaimableRenameEntryWithDirFSO(int actualNumberOfSnapshots, int index)
193+
throws IOException, RocksDBException {
194+
setup(1, actualNumberOfSnapshots, index, 4, 2,
195+
BucketLayout.FILE_SYSTEM_OPTIMIZED);
196+
String volume = getVolumes().get(3);
197+
String bucket = getBuckets().get(1);
198+
index = Math.min(index, actualNumberOfSnapshots);
199+
String value = UUID.randomUUID().toString();
200+
Table<String, OmKeyInfo> keyTable = getMockedTable(Collections.emptyMap());
201+
Table<String, OmDirectoryInfo> directoryTable = getMockedTable(ImmutableMap.of(value, mock(OmDirectoryInfo.class)));
202+
testReclaimableRenameEntryFilter(volume, bucket, index, value, keyTable, directoryTable, index == 0);
203+
}
204+
}

0 commit comments

Comments
 (0)