|
32 | 32 | import java.text.ParseException; |
33 | 33 | import java.text.SimpleDateFormat; |
34 | 34 | import java.time.Instant; |
35 | | -import java.util.List; |
36 | | -import java.util.TimeZone; |
| 35 | +import java.util.ArrayList; |
| 36 | +import java.util.Collections; |
37 | 37 | import java.util.Date; |
| 38 | +import java.util.LinkedHashMap; |
| 39 | +import java.util.List; |
| 40 | +import java.util.Map; |
38 | 41 | import java.util.Set; |
39 | | -import java.util.ArrayList; |
| 42 | +import java.util.TimeZone; |
40 | 43 | import java.util.concurrent.BlockingQueue; |
41 | 44 | import java.util.concurrent.ExecutorService; |
42 | 45 | import java.util.concurrent.Executors; |
|
54 | 57 | import org.apache.hadoop.hdds.scm.ha.SCMNodeDetails; |
55 | 58 | import org.apache.hadoop.hdds.scm.server.SCMDatanodeHeartbeatDispatcher; |
56 | 59 | import org.apache.hadoop.hdds.utils.HddsServerUtil; |
| 60 | +import org.apache.hadoop.hdds.utils.db.Table; |
| 61 | +import org.apache.hadoop.hdds.utils.db.TableIterator; |
57 | 62 | import org.apache.hadoop.hdfs.web.URLConnectionFactory; |
58 | 63 | import org.apache.hadoop.io.IOUtils; |
59 | 64 |
|
@@ -596,6 +601,109 @@ public static long convertToEpochMillis(String dateString, String dateFormat, Ti |
596 | 601 | } |
597 | 602 | } |
598 | 603 |
|
| 604 | + public static boolean validateStartPrefix(String startPrefix) { |
| 605 | + |
| 606 | + // Ensure startPrefix starts with '/' for non-empty values |
| 607 | + startPrefix = startPrefix.startsWith("/") ? startPrefix : "/" + startPrefix; |
| 608 | + |
| 609 | + // Split the path to ensure it's at least at the bucket level (volume/bucket). |
| 610 | + String[] pathComponents = startPrefix.split("/"); |
| 611 | + if (pathComponents.length < 3 || pathComponents[2].isEmpty()) { |
| 612 | + return false; // Invalid if not at bucket level or deeper |
| 613 | + } |
| 614 | + |
| 615 | + return true; |
| 616 | + } |
| 617 | + |
| 618 | + /** |
| 619 | + * Retrieves keys from the specified table based on pagination and prefix filtering. |
| 620 | + * This method handles different scenarios based on the presence of {@code startPrefix} |
| 621 | + * and {@code prevKey}, enabling efficient key retrieval from the table. |
| 622 | + * |
| 623 | + * The method handles the following cases: |
| 624 | + * |
| 625 | + * 1. {@code prevKey} provided, {@code startPrefix} empty: |
| 626 | + * - Seeks to {@code prevKey}, skips it, and returns subsequent records up to the limit. |
| 627 | + * |
| 628 | + * 2. {@code prevKey} empty, {@code startPrefix} empty: |
| 629 | + * - Iterates from the beginning of the table, retrieving all records up to the limit. |
| 630 | + * |
| 631 | + * 3. {@code startPrefix} provided, {@code prevKey} empty: |
| 632 | + * - Seeks to the first key matching {@code startPrefix} and returns all matching keys up to the limit. |
| 633 | + * |
| 634 | + * 4. {@code startPrefix} provided, {@code prevKey} provided: |
| 635 | + * - Seeks to {@code prevKey}, skips it, and returns subsequent keys that match {@code startPrefix}, |
| 636 | + * up to the limit. |
| 637 | + * |
| 638 | + * This method also handles the following {@code limit} scenarios: |
| 639 | + * - If {@code limit == 0} or {@code limit < -1}, no records are returned. |
| 640 | + * - If {@code limit == -1}, all records are returned. |
| 641 | + * - For positive {@code limit}, it retrieves records up to the specified {@code limit}. |
| 642 | + * |
| 643 | + * @param table The table to retrieve keys from. |
| 644 | + * @param startPrefix The search prefix to match keys against. |
| 645 | + * @param limit The maximum number of keys to retrieve. |
| 646 | + * @param prevKey The key to start after for the next set of records. |
| 647 | + * @return A map of keys and their corresponding {@code OmKeyInfo} or {@code RepeatedOmKeyInfo} objects. |
| 648 | + * @throws IOException If there are problems accessing the table. |
| 649 | + */ |
| 650 | + public static <T> Map<String, T> extractKeysFromTable( |
| 651 | + Table<String, T> table, String startPrefix, int limit, String prevKey) |
| 652 | + throws IOException { |
| 653 | + |
| 654 | + Map<String, T> matchedKeys = new LinkedHashMap<>(); |
| 655 | + |
| 656 | + // Null check for the table to prevent NPE during omMetaManager initialization |
| 657 | + if (table == null) { |
| 658 | + log.error("Table object is null. omMetaManager might still be initializing."); |
| 659 | + return Collections.emptyMap(); |
| 660 | + } |
| 661 | + |
| 662 | + // If limit = 0, return an empty result set |
| 663 | + if (limit == 0 || limit < -1) { |
| 664 | + return matchedKeys; |
| 665 | + } |
| 666 | + |
| 667 | + // If limit = -1, set it to Integer.MAX_VALUE to return all records |
| 668 | + int actualLimit = (limit == -1) ? Integer.MAX_VALUE : limit; |
| 669 | + |
| 670 | + try (TableIterator<String, ? extends Table.KeyValue<String, T>> keyIter = table.iterator()) { |
| 671 | + |
| 672 | + // Scenario 1 & 4: prevKey is provided (whether startPrefix is empty or not) |
| 673 | + if (!prevKey.isEmpty()) { |
| 674 | + keyIter.seek(prevKey); |
| 675 | + if (keyIter.hasNext()) { |
| 676 | + keyIter.next(); // Skip the previous key record |
| 677 | + } |
| 678 | + } else if (!startPrefix.isEmpty()) { |
| 679 | + // Scenario 3: startPrefix is provided but prevKey is empty, so seek to startPrefix |
| 680 | + keyIter.seek(startPrefix); |
| 681 | + } |
| 682 | + |
| 683 | + // Scenario 2: Both startPrefix and prevKey are empty (iterate from the start of the table) |
| 684 | + // No seeking needed; just start iterating from the first record in the table |
| 685 | + // This is implicit in the following loop, as the iterator will start from the beginning |
| 686 | + |
| 687 | + // Iterate through the keys while adhering to the limit (if the limit is not zero) |
| 688 | + while (keyIter.hasNext() && matchedKeys.size() < actualLimit) { |
| 689 | + Table.KeyValue<String, T> entry = keyIter.next(); |
| 690 | + String dbKey = entry.getKey(); |
| 691 | + |
| 692 | + // Scenario 3 & 4: If startPrefix is provided, ensure the key matches startPrefix |
| 693 | + if (!startPrefix.isEmpty() && !dbKey.startsWith(startPrefix)) { |
| 694 | + break; // If the key no longer matches the prefix, exit the loop |
| 695 | + } |
| 696 | + |
| 697 | + // Add the valid key-value pair to the results |
| 698 | + matchedKeys.put(dbKey, entry.getValue()); |
| 699 | + } |
| 700 | + } catch (IOException exception) { |
| 701 | + log.error("Error retrieving keys from table for path: {}", startPrefix, exception); |
| 702 | + throw exception; |
| 703 | + } |
| 704 | + return matchedKeys; |
| 705 | + } |
| 706 | + |
599 | 707 | /** |
600 | 708 | * Finds all subdirectories under a parent directory in an FSO bucket. It builds |
601 | 709 | * a list of paths for these subdirectories. These sub-directories are then used |
|
0 commit comments