|
| 1 | +package com.linkedin.metadata.search.elasticsearch.indexbuilder; |
| 2 | + |
| 3 | +import com.linkedin.metadata.utils.elasticsearch.SearchClientShim; |
| 4 | +import java.io.IOException; |
| 5 | +import java.util.Collection; |
| 6 | +import java.util.List; |
| 7 | +import java.util.Set; |
| 8 | +import javax.annotation.Nonnull; |
| 9 | +import lombok.extern.slf4j.Slf4j; |
| 10 | +import org.opensearch.action.admin.indices.alias.get.GetAliasesRequest; |
| 11 | +import org.opensearch.action.admin.indices.delete.DeleteIndexRequest; |
| 12 | +import org.opensearch.client.GetAliasesResponse; |
| 13 | +import org.opensearch.client.RequestOptions; |
| 14 | +import org.opensearch.client.indices.GetIndexRequest; |
| 15 | +import org.opensearch.cluster.metadata.AliasMetadata; |
| 16 | +import org.opensearch.common.unit.TimeValue; |
| 17 | + |
| 18 | +/** |
| 19 | + * Utility class for index deletion operations shared across ESWriteDAO and ESIndexBuilder. Provides |
| 20 | + * common logic for handling both aliases and concrete indices during deletion. |
| 21 | + */ |
| 22 | +@Slf4j |
| 23 | +public class IndexDeletionUtils { |
| 24 | + |
| 25 | + private IndexDeletionUtils() {} |
| 26 | + |
| 27 | + /** Result of resolving an index name to its concrete indices for deletion. */ |
| 28 | + public static class IndexResolutionResult { |
| 29 | + private final Collection<String> indicesToDelete; |
| 30 | + private final String nameToTrack; |
| 31 | + |
| 32 | + public IndexResolutionResult(Collection<String> indicesToDelete, String nameToTrack) { |
| 33 | + this.indicesToDelete = indicesToDelete; |
| 34 | + this.nameToTrack = nameToTrack; |
| 35 | + } |
| 36 | + |
| 37 | + /** The concrete index names to delete */ |
| 38 | + public Collection<String> indicesToDelete() { |
| 39 | + return indicesToDelete; |
| 40 | + } |
| 41 | + |
| 42 | + /** |
| 43 | + * The name to track for recreation (alias name if it was an alias, otherwise the concrete index |
| 44 | + * name) |
| 45 | + */ |
| 46 | + public String nameToTrack() { |
| 47 | + return nameToTrack; |
| 48 | + } |
| 49 | + } |
| 50 | + |
| 51 | + /** |
| 52 | + * Resolves an index name (which may be an alias or concrete index) to the concrete indices that |
| 53 | + * should be deleted, and determines what name to track for recreation. |
| 54 | + * |
| 55 | + * @param searchClient The search client to use for API calls |
| 56 | + * @param indexName The index name to resolve (may be an alias or concrete index) |
| 57 | + * @return IndexResolutionResult containing the concrete indices to delete and name to track, or |
| 58 | + * null if the index doesn't exist |
| 59 | + * @throws IOException If there's an error communicating with Elasticsearch |
| 60 | + */ |
| 61 | + public static IndexResolutionResult resolveIndexForDeletion( |
| 62 | + @Nonnull SearchClientShim<?> searchClient, @Nonnull String indexName) throws IOException { |
| 63 | + |
| 64 | + // Check if it's an alias |
| 65 | + GetAliasesRequest getAliasesRequest = new GetAliasesRequest(indexName); |
| 66 | + GetAliasesResponse aliasesResponse; |
| 67 | + try { |
| 68 | + aliasesResponse = searchClient.getIndexAliases(getAliasesRequest, RequestOptions.DEFAULT); |
| 69 | + } catch (IOException | RuntimeException e) { |
| 70 | + // If getIndexAliases throws, check if it's because index/alias doesn't exist |
| 71 | + if (e.getMessage() != null && e.getMessage().contains("index_not_found_exception")) { |
| 72 | + // Check if it's an actual index |
| 73 | + boolean indexExists = |
| 74 | + searchClient.indexExists(new GetIndexRequest(indexName), RequestOptions.DEFAULT); |
| 75 | + if (!indexExists) { |
| 76 | + log.debug("Index {} does not exist, skipping", indexName); |
| 77 | + return null; |
| 78 | + } |
| 79 | + // Index exists but getIndexAliases failed, treat as concrete index (not an alias) |
| 80 | + aliasesResponse = null; |
| 81 | + } else { |
| 82 | + throw new IOException("Failed to get index aliases for " + indexName, e); |
| 83 | + } |
| 84 | + } |
| 85 | + |
| 86 | + Collection<String> indicesToDelete; |
| 87 | + String nameToTrack; |
| 88 | + |
| 89 | + if (aliasesResponse == null || aliasesResponse.getAliases().isEmpty()) { |
| 90 | + // Not an alias, must be a concrete index - verify it exists |
| 91 | + boolean indexExists = |
| 92 | + searchClient.indexExists(new GetIndexRequest(indexName), RequestOptions.DEFAULT); |
| 93 | + if (!indexExists) { |
| 94 | + log.debug("Index {} does not exist, skipping", indexName); |
| 95 | + return null; |
| 96 | + } |
| 97 | + |
| 98 | + // Check if this concrete index has any aliases pointing to it |
| 99 | + // If so, track the alias name for recreation, not the concrete index name |
| 100 | + GetAliasesRequest aliasesForIndexRequest = new GetAliasesRequest(); |
| 101 | + aliasesForIndexRequest.indices(indexName); |
| 102 | + GetAliasesResponse aliasesForIndex = |
| 103 | + searchClient.getIndexAliases(aliasesForIndexRequest, RequestOptions.DEFAULT); |
| 104 | + |
| 105 | + nameToTrack = indexName; |
| 106 | + if (!aliasesForIndex.getAliases().isEmpty() |
| 107 | + && aliasesForIndex.getAliases().containsKey(indexName)) { |
| 108 | + // Get the alias names that point to this concrete index |
| 109 | + Set<AliasMetadata> aliases = aliasesForIndex.getAliases().get(indexName); |
| 110 | + if (aliases != null && !aliases.isEmpty()) { |
| 111 | + // Use the first alias name (typically there's only one) |
| 112 | + nameToTrack = aliases.iterator().next().alias(); |
| 113 | + log.info( |
| 114 | + "Concrete index {} has alias {}, tracking alias for recreation", |
| 115 | + indexName, |
| 116 | + nameToTrack); |
| 117 | + } |
| 118 | + } |
| 119 | + |
| 120 | + indicesToDelete = List.of(indexName); |
| 121 | + log.info("Resolved concrete index {} for deletion, tracking as {}", indexName, nameToTrack); |
| 122 | + } else { |
| 123 | + // It's an alias, delete the concrete indices behind it |
| 124 | + indicesToDelete = aliasesResponse.getAliases().keySet(); |
| 125 | + nameToTrack = indexName; |
| 126 | + log.info("Resolved alias {} to concrete indices {} for deletion", indexName, indicesToDelete); |
| 127 | + } |
| 128 | + |
| 129 | + return new IndexResolutionResult(indicesToDelete, nameToTrack); |
| 130 | + } |
| 131 | + |
| 132 | + /** |
| 133 | + * Deletes a concrete index with a timeout. |
| 134 | + * |
| 135 | + * @param searchClient The search client to use for API calls |
| 136 | + * @param concreteIndexName The concrete index name to delete (not an alias) |
| 137 | + * @throws IOException If there's an error deleting the index |
| 138 | + */ |
| 139 | + public static void deleteConcreteIndex( |
| 140 | + @Nonnull SearchClientShim<?> searchClient, @Nonnull String concreteIndexName) |
| 141 | + throws IOException { |
| 142 | + DeleteIndexRequest deleteRequest = new DeleteIndexRequest(concreteIndexName); |
| 143 | + deleteRequest.timeout(TimeValue.timeValueSeconds(30)); |
| 144 | + searchClient.deleteIndex(deleteRequest, RequestOptions.DEFAULT); |
| 145 | + log.info("Successfully deleted index {}", concreteIndexName); |
| 146 | + } |
| 147 | + |
| 148 | + /** |
| 149 | + * Deletes an index (handling both aliases and concrete indices). |
| 150 | + * |
| 151 | + * @param searchClient The search client to use for API calls |
| 152 | + * @param indexName The index name to delete (may be an alias or concrete index) |
| 153 | + * @return The name to track for recreation (alias name if applicable), or null if index didn't |
| 154 | + * exist |
| 155 | + * @throws IOException If there's an error deleting the index |
| 156 | + */ |
| 157 | + public static String deleteIndex( |
| 158 | + @Nonnull SearchClientShim<?> searchClient, @Nonnull String indexName) throws IOException { |
| 159 | + IndexResolutionResult resolution = resolveIndexForDeletion(searchClient, indexName); |
| 160 | + if (resolution == null) { |
| 161 | + return null; |
| 162 | + } |
| 163 | + |
| 164 | + for (String concreteIndex : resolution.indicesToDelete()) { |
| 165 | + deleteConcreteIndex(searchClient, concreteIndex); |
| 166 | + } |
| 167 | + |
| 168 | + return resolution.nameToTrack(); |
| 169 | + } |
| 170 | +} |
0 commit comments