/* * SPDX-License-Identifier: Apache-2.0 * * The OpenSearch Contributors require contributions made to * this file be licensed under the Apache-2.0 license or a * compatible open source license. */ /* * Licensed to Elasticsearch under one or more contributor * license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch 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. */ /* * Modifications Copyright OpenSearch Contributors. See * GitHub history for details. */ package org.opensearch.cluster.metadata; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.opensearch.action.ActionListener; import org.opensearch.action.admin.indices.delete.DeleteIndexClusterStateUpdateRequest; import org.opensearch.cluster.AckedClusterStateUpdateTask; import org.opensearch.cluster.ClusterState; import org.opensearch.cluster.RestoreInProgress; import org.opensearch.cluster.ack.ClusterStateUpdateResponse; import org.opensearch.cluster.block.ClusterBlocks; import org.opensearch.cluster.routing.RoutingTable; import org.opensearch.cluster.routing.allocation.AllocationService; import org.opensearch.cluster.service.ClusterManagerTaskKeys; import org.opensearch.cluster.service.ClusterManagerTaskThrottler; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.Priority; import org.opensearch.common.inject.Inject; import org.opensearch.common.settings.Settings; import org.opensearch.common.util.set.Sets; import org.opensearch.core.index.Index; import org.opensearch.snapshots.RestoreService; import org.opensearch.snapshots.SnapshotInProgressException; import org.opensearch.snapshots.SnapshotsService; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; /** * Deletes indices. * * @opensearch.internal */ public class MetadataDeleteIndexService { private static final Logger logger = LogManager.getLogger(MetadataDeleteIndexService.class); private final Settings settings; private final ClusterService clusterService; private final AllocationService allocationService; private final ClusterManagerTaskThrottler.ThrottlingKey deleteIndexTaskKey; @Inject public MetadataDeleteIndexService(Settings settings, ClusterService clusterService, AllocationService allocationService) { this.settings = settings; this.clusterService = clusterService; this.allocationService = allocationService; // Task is onboarded for throttling, it will get retried from associated TransportClusterManagerNodeAction. deleteIndexTaskKey = clusterService.registerClusterManagerTask(ClusterManagerTaskKeys.DELETE_INDEX_KEY, true); } public void deleteIndices( final DeleteIndexClusterStateUpdateRequest request, final ActionListener listener ) { if (request.indices() == null || request.indices().length == 0) { throw new IllegalArgumentException("Index name is required"); } clusterService.submitStateUpdateTask( "delete-index " + Arrays.toString(request.indices()), new AckedClusterStateUpdateTask(Priority.URGENT, request, listener) { @Override protected ClusterStateUpdateResponse newResponse(boolean acknowledged) { return new ClusterStateUpdateResponse(acknowledged); } @Override public ClusterManagerTaskThrottler.ThrottlingKey getClusterManagerThrottlingKey() { return deleteIndexTaskKey; } @Override public ClusterState execute(final ClusterState currentState) { return deleteIndices(currentState, Sets.newHashSet(request.indices())); } } ); } /** * Delete some indices from the cluster state. */ public ClusterState deleteIndices(ClusterState currentState, Set indices) { final Metadata meta = currentState.metadata(); final Set indicesToDelete = new HashSet<>(); final Map backingIndices = new HashMap<>(); for (Index index : indices) { IndexMetadata im = meta.getIndexSafe(index); IndexAbstraction.DataStream parent = meta.getIndicesLookup().get(im.getIndex().getName()).getParentDataStream(); if (parent != null) { if (parent.getWriteIndex().equals(im)) { throw new IllegalArgumentException( "index [" + index.getName() + "] is the write index for data stream [" + parent.getName() + "] and cannot be deleted" ); } else { backingIndices.put(index, parent.getDataStream()); } } indicesToDelete.add(im.getIndex()); } // Check if index deletion conflicts with any running snapshots Set snapshottingIndices = SnapshotsService.snapshottingIndices(currentState, indicesToDelete); if (snapshottingIndices.isEmpty() == false) { throw new SnapshotInProgressException( "Cannot delete indices that are being snapshotted: " + snapshottingIndices + ". Try again after snapshot finishes or cancel the currently running snapshot." ); } RoutingTable.Builder routingTableBuilder = RoutingTable.builder(currentState.routingTable()); Metadata.Builder metadataBuilder = Metadata.builder(meta); ClusterBlocks.Builder clusterBlocksBuilder = ClusterBlocks.builder().blocks(currentState.blocks()); final IndexGraveyard.Builder graveyardBuilder = IndexGraveyard.builder(metadataBuilder.indexGraveyard()); final int previousGraveyardSize = graveyardBuilder.tombstones().size(); for (final Index index : indices) { String indexName = index.getName(); logger.info("{} deleting index", index); routingTableBuilder.remove(indexName); clusterBlocksBuilder.removeIndexBlocks(indexName); metadataBuilder.remove(indexName); if (backingIndices.containsKey(index)) { DataStream parent = metadataBuilder.dataStream(backingIndices.get(index).getName()); metadataBuilder.put(parent.removeBackingIndex(index)); } } // add tombstones to the cluster state for each deleted index final IndexGraveyard currentGraveyard = graveyardBuilder.addTombstones(indices).build(settings); metadataBuilder.indexGraveyard(currentGraveyard); // the new graveyard set on the metadata logger.trace( "{} tombstones purged from the cluster state. Previous tombstone size: {}. Current tombstone size: {}.", graveyardBuilder.getNumPurged(), previousGraveyardSize, currentGraveyard.getTombstones().size() ); Metadata newMetadata = metadataBuilder.build(); ClusterBlocks blocks = clusterBlocksBuilder.build(); // update snapshot restore entries Map customs = currentState.getCustoms(); final RestoreInProgress restoreInProgress = currentState.custom(RestoreInProgress.TYPE, RestoreInProgress.EMPTY); RestoreInProgress updatedRestoreInProgress = RestoreService.updateRestoreStateWithDeletedIndices(restoreInProgress, indices); if (updatedRestoreInProgress != restoreInProgress) { final Map builder = new HashMap<>(customs); builder.put(RestoreInProgress.TYPE, updatedRestoreInProgress); customs = Collections.unmodifiableMap(builder); } return allocationService.reroute( ClusterState.builder(currentState) .routingTable(routingTableBuilder.build()) .metadata(newMetadata) .blocks(blocks) .customs(customs) .build(), "deleted indices [" + indices + "]" ); } }