/*
* 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.
*/
package org.elasticsearch.cluster.metadata;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesClusterStateUpdateRequest;
import org.elasticsearch.cluster.AckedClusterStateUpdateTask;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.ack.ClusterStateUpdateResponse;
import org.elasticsearch.cluster.metadata.AliasAction.NewAliasValidator;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.Priority;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.index.Index;
import org.elasticsearch.index.IndexNotFoundException;
import org.elasticsearch.index.IndexService;
import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.indices.IndicesService;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import static java.util.Collections.emptyList;
import static org.elasticsearch.indices.cluster.IndicesClusterStateService.AllocatedIndices.IndexRemovalReason.NO_LONGER_ASSIGNED;
/**
* Service responsible for submitting add and remove aliases requests
*/
public class MetaDataIndexAliasesService extends AbstractComponent {
private final ClusterService clusterService;
private final IndicesService indicesService;
private final AliasValidator aliasValidator;
private final MetaDataDeleteIndexService deleteIndexService;
private final NamedXContentRegistry xContentRegistry;
@Inject
public MetaDataIndexAliasesService(Settings settings, ClusterService clusterService, IndicesService indicesService,
AliasValidator aliasValidator, MetaDataDeleteIndexService deleteIndexService, NamedXContentRegistry xContentRegistry) {
super(settings);
this.clusterService = clusterService;
this.indicesService = indicesService;
this.aliasValidator = aliasValidator;
this.deleteIndexService = deleteIndexService;
this.xContentRegistry = xContentRegistry;
}
public void indicesAliases(final IndicesAliasesClusterStateUpdateRequest request,
final ActionListener<ClusterStateUpdateResponse> listener) {
clusterService.submitStateUpdateTask("index-aliases",
new AckedClusterStateUpdateTask<ClusterStateUpdateResponse>(Priority.URGENT, request, listener) {
@Override
protected ClusterStateUpdateResponse newResponse(boolean acknowledged) {
return new ClusterStateUpdateResponse(acknowledged);
}
@Override
public ClusterState execute(ClusterState currentState) {
return innerExecute(currentState, request.actions());
}
});
}
ClusterState innerExecute(ClusterState currentState, Iterable<AliasAction> actions) {
List<Index> indicesToClose = new ArrayList<>();
Map<String, IndexService> indices = new HashMap<>();
try {
boolean changed = false;
// Gather all the indexes that must be removed first so:
// 1. We don't cause error when attempting to replace an index with a alias of the same name.
// 2. We don't allow removal of aliases from indexes that we're just going to delete anyway. That'd be silly.
Set<Index> indicesToDelete = new HashSet<>();
for (AliasAction action : actions) {
if (action.removeIndex()) {
IndexMetaData index = currentState.metaData().getIndices().get(action.getIndex());
if (index == null) {
throw new IndexNotFoundException(action.getIndex());
}
indicesToDelete.add(index.getIndex());
changed = true;
}
}
// Remove the indexes if there are any to remove
if (changed) {
currentState = deleteIndexService.deleteIndices(currentState, indicesToDelete);
}
MetaData.Builder metadata = MetaData.builder(currentState.metaData());
// Run the remaining alias actions
for (AliasAction action : actions) {
if (action.removeIndex()) {
// Handled above
continue;
}
IndexMetaData index = metadata.get(action.getIndex());
if (index == null) {
throw new IndexNotFoundException(action.getIndex());
}
NewAliasValidator newAliasValidator = (alias, indexRouting, filter) -> {
/* It is important that we look up the index using the metadata builder we are modifying so we can remove an
* index and replace it with an alias. */
Function<String, IndexMetaData> indexLookup = name -> metadata.get(name);
aliasValidator.validateAlias(alias, action.getIndex(), indexRouting, indexLookup);
if (Strings.hasLength(filter)) {
IndexService indexService = indices.get(index.getIndex());
if (indexService == null) {
indexService = indicesService.indexService(index.getIndex());
if (indexService == null) {
// temporarily create the index and add mappings so we can parse the filter
try {
indexService = indicesService.createIndex(index, emptyList());
indicesToClose.add(index.getIndex());
} catch (IOException e) {
throw new ElasticsearchException("Failed to create temporary index for parsing the alias", e);
}
indexService.mapperService().merge(index, MapperService.MergeReason.MAPPING_RECOVERY, false);
}
indices.put(action.getIndex(), indexService);
}
// the context is only used for validation so it's fine to pass fake values for the shard id and the current
// timestamp
aliasValidator.validateAliasFilter(alias, filter, indexService.newQueryShardContext(0, null, () -> 0L),
xContentRegistry);
}
};
changed |= action.apply(newAliasValidator, metadata, index);
}
if (changed) {
ClusterState updatedState = ClusterState.builder(currentState).metaData(metadata).build();
// even though changes happened, they resulted in 0 actual changes to metadata
// i.e. remove and add the same alias to the same index
if (!updatedState.metaData().equalsAliases(currentState.metaData())) {
return updatedState;
}
}
return currentState;
} finally {
for (Index index : indicesToClose) {
indicesService.removeIndex(index, NO_LONGER_ASSIGNED, "created for alias processing");
}
}
}
}