Skip to content

Commit 0b8875b

Browse files
authored
HDDS-11747. Add ozone debug CLI to get keys that use certain containers (#9469)
1 parent 064012a commit 0b8875b

4 files changed

Lines changed: 583 additions & 2 deletions

File tree

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ protected OmMetadataManagerImpl(OzoneConfiguration conf, File dir, String name)
266266
* @param name - Checkpoint directory name.
267267
* @throws IOException
268268
*/
269-
protected OmMetadataManagerImpl(OzoneConfiguration conf, File dir, String name, boolean readOnly)
269+
public OmMetadataManagerImpl(OzoneConfiguration conf, File dir, String name, boolean readOnly)
270270
throws IOException {
271271
lock = new OmReadOnlyLock();
272272
hierarchicalLockManager = new ReadOnlyHierarchicalResourceLockManager();
Lines changed: 364 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,364 @@
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.debug.om;
19+
20+
import static org.apache.hadoop.ozone.OzoneConsts.OM_KEY_PREFIX;
21+
22+
import com.fasterxml.jackson.databind.ObjectMapper;
23+
import com.fasterxml.jackson.databind.node.ArrayNode;
24+
import com.fasterxml.jackson.databind.node.ObjectNode;
25+
import java.io.File;
26+
import java.io.IOException;
27+
import java.io.PrintWriter;
28+
import java.util.ArrayList;
29+
import java.util.Arrays;
30+
import java.util.HashMap;
31+
import java.util.HashSet;
32+
import java.util.List;
33+
import java.util.Map;
34+
import java.util.Set;
35+
import java.util.concurrent.Callable;
36+
import java.util.stream.Collectors;
37+
import org.apache.commons.io.FileUtils;
38+
import org.apache.commons.lang3.StringUtils;
39+
import org.apache.commons.lang3.tuple.Pair;
40+
import org.apache.hadoop.hdds.cli.AbstractSubcommand;
41+
import org.apache.hadoop.hdds.conf.ConfigurationSource;
42+
import org.apache.hadoop.hdds.conf.OzoneConfiguration;
43+
import org.apache.hadoop.hdds.utils.db.DBColumnFamilyDefinition;
44+
import org.apache.hadoop.hdds.utils.db.DBStore;
45+
import org.apache.hadoop.hdds.utils.db.DBStoreBuilder;
46+
import org.apache.hadoop.hdds.utils.db.LongCodec;
47+
import org.apache.hadoop.hdds.utils.db.StringCodec;
48+
import org.apache.hadoop.hdds.utils.db.Table;
49+
import org.apache.hadoop.hdds.utils.db.TableIterator;
50+
import org.apache.hadoop.hdds.utils.db.cache.TableCache.CacheType;
51+
import org.apache.hadoop.ozone.om.codec.OMDBDefinition;
52+
import org.apache.hadoop.ozone.om.helpers.OmBucketInfo;
53+
import org.apache.hadoop.ozone.om.helpers.OmDirectoryInfo;
54+
import org.apache.hadoop.ozone.om.helpers.OmKeyInfo;
55+
import org.apache.hadoop.ozone.om.helpers.OmVolumeArgs;
56+
import picocli.CommandLine;
57+
58+
/**
59+
* Tool to map full key paths that use the specified containers.
60+
* Note: Currently only processes FSO layout buckets.
61+
*/
62+
@CommandLine.Command(
63+
name = "container-key-mapping",
64+
aliases = "ckm",
65+
description = "Maps full key paths that use the specified containers. " +
66+
"Note: A container can have both FSO and OBS keys. Currently this tool processes only FSO keys")
67+
public class ContainerToKeyMapping extends AbstractSubcommand implements Callable<Void> {
68+
private static final String DIRTREE_DB_NAME = "omdirtree.db";
69+
private static final String DIRTREE_TABLE_NAME = "dirTreeTable";
70+
private static final DBColumnFamilyDefinition<Long, String> DIRTREE_TABLE_DEFINITION =
71+
new DBColumnFamilyDefinition<>(DIRTREE_TABLE_NAME,
72+
LongCodec.get(),
73+
StringCodec.get());
74+
75+
@CommandLine.ParentCommand
76+
private OMDebug parent;
77+
78+
@CommandLine.Option(names = {"--containers"},
79+
required = true,
80+
description = "Comma separated Container IDs")
81+
private String containers;
82+
83+
private DBStore omDbStore;
84+
private Table<String, OmVolumeArgs> volumeTable;
85+
private Table<String, OmBucketInfo> bucketTable;
86+
private Table<String, OmDirectoryInfo> directoryTable;
87+
private Table<String, OmKeyInfo> fileTable;
88+
private DBStore dirTreeDbStore;
89+
private Table<Long, String> dirTreeTable;
90+
// Cache volume IDs to avoid repeated lookups
91+
private final Map<String, Long> volumeCache = new HashMap<>();
92+
private ConfigurationSource conf;
93+
94+
// TODO: Add support to OBS keys (HDDS-14118)
95+
@Override
96+
public Void call() throws Exception {
97+
err().println("Note: A container can have both FSO and OBS keys. Currently this tool processes only FSO keys");
98+
99+
String dbPath = parent.getDbPath();
100+
// Parse container IDs
101+
Set<Long> containerIDs = Arrays.stream(containers.split(","))
102+
.map(String::trim)
103+
.filter(s -> !s.isEmpty())
104+
.map(Long::parseLong)
105+
.collect(Collectors.toSet());
106+
107+
if (containerIDs.isEmpty()) {
108+
err().println("No valid container IDs provided");
109+
return null;
110+
}
111+
112+
conf = new OzoneConfiguration();
113+
File dbFile = new File(dbPath);
114+
115+
try (PrintWriter writer = out()) {
116+
omDbStore = DBStoreBuilder.newBuilder(conf, OMDBDefinition.get(), dbFile.getName(),
117+
dbFile.getParentFile().toPath())
118+
.setOpenReadOnly(true)
119+
.build();
120+
121+
volumeTable = OMDBDefinition.VOLUME_TABLE_DEF.getTable(omDbStore, CacheType.NO_CACHE);
122+
bucketTable = OMDBDefinition.BUCKET_TABLE_DEF.getTable(omDbStore, CacheType.NO_CACHE);
123+
directoryTable = OMDBDefinition.DIRECTORY_TABLE_DEF.getTable(omDbStore, CacheType.NO_CACHE);
124+
fileTable = OMDBDefinition.FILE_TABLE_DEF.getTable(omDbStore, CacheType.NO_CACHE);
125+
126+
openDirTreeDB(dbPath);
127+
retrieve(writer, containerIDs);
128+
} catch (Exception e) {
129+
err().println("Failed to open RocksDB: " + e);
130+
throw e;
131+
} finally {
132+
closeDirTreeDB(dbPath);
133+
if (omDbStore != null) {
134+
omDbStore.close();
135+
}
136+
}
137+
return null;
138+
}
139+
140+
private void openDirTreeDB(String dbPath) throws IOException {
141+
File dirTreeDbPath = new File(new File(dbPath).getParentFile(), DIRTREE_DB_NAME);
142+
// Delete the DB from the last run if it exists.
143+
if (dirTreeDbPath.exists()) {
144+
FileUtils.deleteDirectory(dirTreeDbPath);
145+
}
146+
147+
dirTreeDbStore = DBStoreBuilder.newBuilder(conf)
148+
.setName(DIRTREE_DB_NAME)
149+
.setPath(dirTreeDbPath.getParentFile().toPath())
150+
.addTable(DIRTREE_TABLE_DEFINITION.getName())
151+
.build();
152+
dirTreeTable = dirTreeDbStore.getTable(DIRTREE_TABLE_DEFINITION.getName(),
153+
DIRTREE_TABLE_DEFINITION.getKeyCodec(), DIRTREE_TABLE_DEFINITION.getValueCodec());
154+
}
155+
156+
private void closeDirTreeDB(String dbPath) throws IOException {
157+
if (dirTreeDbStore != null) {
158+
dirTreeDbStore.close();
159+
}
160+
File dirTreeDbPath = new File(new File(dbPath).getParentFile(), DIRTREE_DB_NAME);
161+
if (dirTreeDbPath.exists()) {
162+
FileUtils.deleteDirectory(dirTreeDbPath);
163+
}
164+
}
165+
166+
private void retrieve(PrintWriter writer, Set<Long> containerIds) {
167+
// Build dir tree
168+
Map<Long, Pair<Long, String>> bucketVolMap = new HashMap<>();
169+
try {
170+
prepareDirIdTree(bucketVolMap);
171+
} catch (Exception e) {
172+
err().println("Exception occurred reading directory Table, " + e);
173+
return;
174+
}
175+
176+
// Map to collect keys per container
177+
Map<Long, List<String>> containerToKeysMap = new HashMap<>();
178+
// Track unreferenced keys count per container
179+
Map<Long, Long> unreferencedCountMap = new HashMap<>();
180+
for (Long containerId : containerIds) {
181+
containerToKeysMap.put(containerId, new ArrayList<>());
182+
unreferencedCountMap.put(containerId, 0L);
183+
}
184+
185+
// Iterate file table and filter for container
186+
try (TableIterator<String, ? extends Table.KeyValue<String, OmKeyInfo>> fileIterator =
187+
fileTable.iterator()) {
188+
189+
while (fileIterator.hasNext()) {
190+
Table.KeyValue<String, OmKeyInfo> entry = fileIterator.next();
191+
OmKeyInfo keyInfo = entry.getValue();
192+
193+
// Find which containers this key uses
194+
Set<Long> keyContainers = new HashSet<>();
195+
keyInfo.getKeyLocationVersions().forEach(
196+
e -> e.getLocationList().forEach(
197+
blk -> {
198+
long cid = blk.getBlockID().getContainerID();
199+
if (containerIds.contains(cid)) {
200+
keyContainers.add(cid);
201+
}
202+
}));
203+
204+
if (!keyContainers.isEmpty()) {
205+
// Reconstruct full path
206+
String fullPath = reconstructFullPath(keyInfo, bucketVolMap, unreferencedCountMap, keyContainers);
207+
if (fullPath != null) {
208+
for (Long containerId : keyContainers) {
209+
containerToKeysMap.get(containerId).add(fullPath);
210+
}
211+
}
212+
}
213+
}
214+
} catch (Exception e) {
215+
err().println("Exception occurred reading file Table, " + e);
216+
return;
217+
}
218+
jsonOutput(writer, containerToKeysMap, unreferencedCountMap);
219+
}
220+
221+
private void prepareDirIdTree(Map<Long, Pair<Long, String>> bucketVolMap) throws Exception {
222+
// Add bucket volume tree
223+
try (TableIterator<String, ? extends Table.KeyValue<String, OmBucketInfo>> bucketIterator =
224+
bucketTable.iterator()) {
225+
226+
while (bucketIterator.hasNext()) {
227+
Table.KeyValue<String, OmBucketInfo> entry = bucketIterator.next();
228+
OmBucketInfo bucketInfo = entry.getValue();
229+
String volumeName = bucketInfo.getVolumeName();
230+
231+
// Get volume ID from volume table
232+
Long volumeId = getVolumeId(volumeName);
233+
if (volumeId == null) {
234+
continue;
235+
}
236+
237+
bucketVolMap.put(bucketInfo.getObjectID(), Pair.of(volumeId, bucketInfo.getBucketName()));
238+
bucketVolMap.putIfAbsent(volumeId, Pair.of(null, volumeName));
239+
}
240+
}
241+
242+
// Add dir tree
243+
try (TableIterator<String, ? extends Table.KeyValue<String, OmDirectoryInfo>> directoryIterator =
244+
directoryTable.iterator()) {
245+
246+
while (directoryIterator.hasNext()) {
247+
Table.KeyValue<String, OmDirectoryInfo> entry = directoryIterator.next();
248+
OmDirectoryInfo dirInfo = entry.getValue();
249+
addToDirTree(dirInfo.getObjectID(), dirInfo.getParentObjectID(), dirInfo.getName());
250+
}
251+
}
252+
}
253+
254+
private String reconstructFullPath(OmKeyInfo keyInfo, Map<Long, Pair<Long, String>> bucketVolMap,
255+
Map<Long, Long> unreferencedCountMap, Set<Long> keyContainers) throws Exception {
256+
StringBuilder sb = new StringBuilder(keyInfo.getKeyName());
257+
Long prvParent = keyInfo.getParentObjectID();
258+
259+
while (prvParent != null) {
260+
// Check reached for bucket volume level
261+
if (bucketVolMap.containsKey(prvParent)) {
262+
Pair<Long, String> nameParentPair = bucketVolMap.get(prvParent);
263+
sb.insert(0, nameParentPair.getValue() + OM_KEY_PREFIX);
264+
prvParent = nameParentPair.getKey();
265+
if (null == prvParent) {
266+
return sb.toString();
267+
}
268+
continue;
269+
}
270+
271+
// Check dir tree
272+
Pair<Long, String> nameParentPair = getFromDirTree(prvParent);
273+
if (nameParentPair == null) {
274+
// Parent not found - increment unreferenced count for all containers this key uses
275+
for (Long containerId : keyContainers) {
276+
unreferencedCountMap.put(containerId, unreferencedCountMap.get(containerId) + 1);
277+
}
278+
break;
279+
}
280+
sb.insert(0, nameParentPair.getValue() + OM_KEY_PREFIX);
281+
prvParent = nameParentPair.getKey();
282+
}
283+
return null;
284+
}
285+
286+
private Pair<Long, String> getFromDirTree(Long objectId) throws IOException {
287+
String val = dirTreeTable.get(objectId);
288+
if (val == null) {
289+
return null;
290+
}
291+
return getDirParentNamePair(val);
292+
}
293+
294+
public static Pair<Long, String> getDirParentNamePair(String val) {
295+
int hashIdx = val.lastIndexOf('#');
296+
String strParentId = val.substring(hashIdx + 1);
297+
Long parentId = null;
298+
if (!StringUtils.isEmpty(strParentId)) {
299+
parentId = Long.parseLong(strParentId);
300+
}
301+
return Pair.of(parentId, val.substring(0, hashIdx));
302+
}
303+
304+
private Long getVolumeId(String volumeName) throws Exception {
305+
if (volumeCache.containsKey(volumeName)) {
306+
return volumeCache.get(volumeName);
307+
}
308+
309+
String volumeKey = OM_KEY_PREFIX + volumeName;
310+
OmVolumeArgs volumeArgs = volumeTable.get(volumeKey);
311+
312+
if (volumeArgs != null) {
313+
Long volumeId = volumeArgs.getObjectID();
314+
volumeCache.put(volumeName, volumeId);
315+
return volumeId;
316+
}
317+
return null;
318+
}
319+
320+
private void addToDirTree(Long objectId, Long parentId, String name) throws IOException {
321+
if (null == parentId) {
322+
dirTreeTable.put(objectId, name + "#");
323+
} else {
324+
dirTreeTable.put(objectId, name + "#" + parentId);
325+
}
326+
}
327+
328+
private void jsonOutput(PrintWriter writer, Map<Long, List<String>> containerToKeysMap,
329+
Map<Long, Long> unreferencedCountMap) {
330+
try {
331+
ObjectMapper mapper = new ObjectMapper();
332+
ObjectNode root = mapper.createObjectNode();
333+
ObjectNode containersNode = mapper.createObjectNode();
334+
335+
for (Map.Entry<Long, List<String>> entry : containerToKeysMap.entrySet()) {
336+
Long containerId = entry.getKey();
337+
ObjectNode containerNode = mapper.createObjectNode();
338+
ArrayNode keysArray = mapper.createArrayNode();
339+
340+
for (String key : entry.getValue()) {
341+
keysArray.add(key);
342+
}
343+
344+
containerNode.set("keys", keysArray);
345+
containerNode.put("numOfKeys", entry.getValue().size());
346+
347+
// Add unreferenced count if > 0
348+
long unreferencedCount = unreferencedCountMap.get(containerId);
349+
if (unreferencedCount > 0) {
350+
containerNode.put("unreferencedKeys", unreferencedCount);
351+
}
352+
353+
containersNode.set(containerId.toString(), containerNode);
354+
}
355+
356+
root.set("containers", containersNode);
357+
writer.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(root));
358+
writer.flush();
359+
} catch (Exception e) {
360+
err().println("Error writing JSON output: " + e.getMessage());
361+
}
362+
}
363+
}
364+

hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/debug/om/OMDebug.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@
2929
description = "Debug commands related to OM.",
3030
subcommands = {
3131
CompactionLogDagPrinter.class,
32-
PrefixParser.class
32+
PrefixParser.class,
33+
ContainerToKeyMapping.class
3334
}
3435
)
3536
@MetaInfServices(DebugSubcommand.class)

0 commit comments

Comments
 (0)