org.elasticsearch.cluster.metadata.Metadata Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of elasticsearch Show documentation
Show all versions of elasticsearch Show documentation
Elasticsearch - Open Source, Distributed, RESTful Search Engine
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
package org.elasticsearch.cluster.metadata;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.lucene.util.CollectionUtil;
import org.elasticsearch.TransportVersion;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.Diff;
import org.elasticsearch.cluster.Diffable;
import org.elasticsearch.cluster.DiffableUtils;
import org.elasticsearch.cluster.NamedDiffable;
import org.elasticsearch.cluster.NamedDiffableValueSerializer;
import org.elasticsearch.cluster.SimpleDiffable;
import org.elasticsearch.cluster.block.ClusterBlock;
import org.elasticsearch.cluster.block.ClusterBlockLevel;
import org.elasticsearch.cluster.coordination.CoordinationMetadata;
import org.elasticsearch.cluster.coordination.PublicationTransportHandler;
import org.elasticsearch.cluster.metadata.IndexAbstraction.ConcreteIndex;
import org.elasticsearch.cluster.routing.RoutingTable;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.UUIDs;
import org.elasticsearch.common.collect.ImmutableOpenMap;
import org.elasticsearch.common.collect.Iterators;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.VersionedNamedWriteable;
import org.elasticsearch.common.regex.Regex;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Setting.Property;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.ArrayUtils;
import org.elasticsearch.common.util.Maps;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.common.xcontent.ChunkedToXContent;
import org.elasticsearch.common.xcontent.ChunkedToXContentHelper;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.XContentParserUtils;
import org.elasticsearch.core.Nullable;
import org.elasticsearch.gateway.MetadataStateFormat;
import org.elasticsearch.index.Index;
import org.elasticsearch.index.IndexMode;
import org.elasticsearch.index.IndexNotFoundException;
import org.elasticsearch.index.IndexSettings;
import org.elasticsearch.index.IndexVersion;
import org.elasticsearch.plugins.MapperPlugin;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.transport.Transports;
import org.elasticsearch.xcontent.NamedObjectNotFoundException;
import org.elasticsearch.xcontent.NamedXContentRegistry;
import org.elasticsearch.xcontent.ToXContent;
import org.elasticsearch.xcontent.XContentBuilder;
import org.elasticsearch.xcontent.XContentParser;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.elasticsearch.cluster.metadata.LifecycleExecutionState.ILM_CUSTOM_METADATA_KEY;
import static org.elasticsearch.common.settings.Settings.readSettingsFromStream;
import static org.elasticsearch.index.IndexSettings.PREFER_ILM_SETTING;
/**
* {@link Metadata} is the part of the {@link ClusterState} which persists across restarts. This persistence is XContent-based, so a
* round-trip through XContent must be faithful in {@link XContentContext#GATEWAY} context.
*
* The details of how this is persisted are covered in {@link org.elasticsearch.gateway.PersistedClusterStateService}.
*
*/
public class Metadata implements Iterable, Diffable, ChunkedToXContent {
private static final Logger logger = LogManager.getLogger(Metadata.class);
public static final Runnable ON_NEXT_INDEX_FIND_MAPPINGS_NOOP = () -> {};
public static final String ALL = "_all";
public static final String UNKNOWN_CLUSTER_UUID = "_na_";
public enum XContentContext {
/* Custom metadata should be returned as part of API call */
API,
/* Custom metadata should be stored as part of the persistent cluster state */
GATEWAY,
/* Custom metadata should be stored as part of a snapshot */
SNAPSHOT
}
/**
* Indicates that this custom metadata will be returned as part of an API call but will not be persisted
*/
public static EnumSet API_ONLY = EnumSet.of(XContentContext.API);
/**
* Indicates that this custom metadata will be returned as part of an API call and will be persisted between
* node restarts, but will not be a part of a snapshot global state
*/
public static EnumSet API_AND_GATEWAY = EnumSet.of(XContentContext.API, XContentContext.GATEWAY);
/**
* Indicates that this custom metadata will be returned as part of an API call and stored as a part of
* a snapshot global state, but will not be persisted between node restarts
*/
public static EnumSet API_AND_SNAPSHOT = EnumSet.of(XContentContext.API, XContentContext.SNAPSHOT);
/**
* Indicates that this custom metadata will be returned as part of an API call, stored as a part of
* a snapshot global state, and will be persisted between node restarts
*/
public static EnumSet ALL_CONTEXTS = EnumSet.allOf(XContentContext.class);
/**
* Custom metadata that persists (via XContent) across restarts. The deserialization method for each implementation must be registered
* with the {@link NamedXContentRegistry}.
*/
public interface Custom extends NamedDiffable, ChunkedToXContent {
EnumSet context();
/**
* @return true if this custom could be restored from snapshot
*/
default boolean isRestorable() {
return context().contains(XContentContext.SNAPSHOT);
}
}
public static final Setting SETTING_READ_ONLY_SETTING = Setting.boolSetting(
"cluster.blocks.read_only",
false,
Property.Dynamic,
Property.NodeScope
);
public static final ClusterBlock CLUSTER_READ_ONLY_BLOCK = new ClusterBlock(
6,
"cluster read-only (api)",
false,
false,
false,
RestStatus.FORBIDDEN,
EnumSet.of(ClusterBlockLevel.WRITE, ClusterBlockLevel.METADATA_WRITE)
);
public static final Setting SETTING_READ_ONLY_ALLOW_DELETE_SETTING = Setting.boolSetting(
"cluster.blocks.read_only_allow_delete",
false,
Property.Dynamic,
Property.NodeScope
);
public static final ClusterBlock CLUSTER_READ_ONLY_ALLOW_DELETE_BLOCK = new ClusterBlock(
13,
"cluster read-only / allow delete (api)",
false,
false,
true,
RestStatus.FORBIDDEN,
EnumSet.of(ClusterBlockLevel.WRITE, ClusterBlockLevel.METADATA_WRITE)
);
public static final Metadata EMPTY_METADATA = builder().build();
public static final String CONTEXT_MODE_PARAM = "context_mode";
public static final String CONTEXT_MODE_SNAPSHOT = XContentContext.SNAPSHOT.toString();
public static final String CONTEXT_MODE_GATEWAY = XContentContext.GATEWAY.toString();
public static final String CONTEXT_MODE_API = XContentContext.API.toString();
public static final String DEDUPLICATED_MAPPINGS_PARAM = "deduplicated_mappings";
public static final String GLOBAL_STATE_FILE_PREFIX = "global-";
private static final NamedDiffableValueSerializer CUSTOM_VALUE_SERIALIZER = new NamedDiffableValueSerializer<>(Custom.class);
private final String clusterUUID;
private final boolean clusterUUIDCommitted;
private final long version;
private final CoordinationMetadata coordinationMetadata;
private final Settings transientSettings;
private final Settings persistentSettings;
private final Settings settings;
private final DiffableStringMap hashesOfConsistentSettings;
private final ImmutableOpenMap indices;
private final ImmutableOpenMap> aliasedIndices;
private final ImmutableOpenMap templates;
private final ImmutableOpenMap customs;
private final Map reservedStateMetadata;
private final transient int totalNumberOfShards; // Transient ? not serializable anyway?
private final int totalOpenIndexShards;
private final String[] allIndices;
private final String[] visibleIndices;
private final String[] allOpenIndices;
private final String[] visibleOpenIndices;
private final String[] allClosedIndices;
private final String[] visibleClosedIndices;
private volatile SortedMap indicesLookup;
private final Map mappingsByHash;
private final IndexVersion oldestIndexVersion;
private Metadata(
String clusterUUID,
boolean clusterUUIDCommitted,
long version,
CoordinationMetadata coordinationMetadata,
Settings transientSettings,
Settings persistentSettings,
Settings settings,
DiffableStringMap hashesOfConsistentSettings,
int totalNumberOfShards,
int totalOpenIndexShards,
ImmutableOpenMap indices,
ImmutableOpenMap> aliasedIndices,
ImmutableOpenMap templates,
ImmutableOpenMap customs,
String[] allIndices,
String[] visibleIndices,
String[] allOpenIndices,
String[] visibleOpenIndices,
String[] allClosedIndices,
String[] visibleClosedIndices,
SortedMap indicesLookup,
Map mappingsByHash,
IndexVersion oldestIndexVersion,
Map reservedStateMetadata
) {
this.clusterUUID = clusterUUID;
this.clusterUUIDCommitted = clusterUUIDCommitted;
this.version = version;
this.coordinationMetadata = coordinationMetadata;
this.transientSettings = transientSettings;
this.persistentSettings = persistentSettings;
this.settings = settings;
this.hashesOfConsistentSettings = hashesOfConsistentSettings;
this.indices = indices;
this.aliasedIndices = aliasedIndices;
this.customs = customs;
this.templates = templates;
this.totalNumberOfShards = totalNumberOfShards;
this.totalOpenIndexShards = totalOpenIndexShards;
this.allIndices = allIndices;
this.visibleIndices = visibleIndices;
this.allOpenIndices = allOpenIndices;
this.visibleOpenIndices = visibleOpenIndices;
this.allClosedIndices = allClosedIndices;
this.visibleClosedIndices = visibleClosedIndices;
this.indicesLookup = indicesLookup;
this.mappingsByHash = mappingsByHash;
this.oldestIndexVersion = oldestIndexVersion;
this.reservedStateMetadata = reservedStateMetadata;
assert assertConsistent();
}
private boolean assertConsistent() {
final var lookup = this.indicesLookup;
final var dsMetadata = custom(DataStreamMetadata.TYPE, DataStreamMetadata.EMPTY);
assert lookup == null || lookup.equals(Builder.buildIndicesLookup(dsMetadata, indices));
try {
Builder.ensureNoNameCollisions(aliasedIndices.keySet(), indices, dsMetadata);
} catch (Exception e) {
assert false : e;
}
assert Builder.assertDataStreams(indices, dsMetadata);
assert Set.of(allIndices).equals(indices.keySet());
final Function, Set> indicesByPredicate = predicate -> indices.entrySet()
.stream()
.filter(entry -> predicate.test(entry.getValue()))
.map(Map.Entry::getKey)
.collect(Collectors.toUnmodifiableSet());
assert Set.of(allOpenIndices).equals(indicesByPredicate.apply(idx -> idx.getState() == IndexMetadata.State.OPEN));
assert Set.of(allClosedIndices).equals(indicesByPredicate.apply(idx -> idx.getState() == IndexMetadata.State.CLOSE));
assert Set.of(visibleIndices).equals(indicesByPredicate.apply(idx -> idx.isHidden() == false));
assert Set.of(visibleOpenIndices)
.equals(indicesByPredicate.apply(idx -> idx.isHidden() == false && idx.getState() == IndexMetadata.State.OPEN));
assert Set.of(visibleClosedIndices)
.equals(indicesByPredicate.apply(idx -> idx.isHidden() == false && idx.getState() == IndexMetadata.State.CLOSE));
return true;
}
public Metadata withIncrementedVersion() {
return new Metadata(
clusterUUID,
clusterUUIDCommitted,
version + 1,
coordinationMetadata,
transientSettings,
persistentSettings,
settings,
hashesOfConsistentSettings,
totalNumberOfShards,
totalOpenIndexShards,
indices,
aliasedIndices,
templates,
customs,
allIndices,
visibleIndices,
allOpenIndices,
visibleOpenIndices,
allClosedIndices,
visibleClosedIndices,
indicesLookup,
mappingsByHash,
oldestIndexVersion,
reservedStateMetadata
);
}
/**
* Given an index and lifecycle state, returns a metadata where the lifecycle state will be
* associated with the given index.
*
* The passed-in index must already be present in the cluster state, this method cannot
* be used to add an index.
*
* @param index A non-null index
* @param lifecycleState A non-null lifecycle execution state
* @return a Metadata
instance where the index has the provided lifecycle state
*/
public Metadata withLifecycleState(final Index index, final LifecycleExecutionState lifecycleState) {
Objects.requireNonNull(index, "index must not be null");
Objects.requireNonNull(lifecycleState, "lifecycleState must not be null");
IndexMetadata indexMetadata = getIndexSafe(index);
if (lifecycleState.equals(indexMetadata.getLifecycleExecutionState())) {
return this;
}
// build a new index metadata with the version incremented and the new lifecycle state
IndexMetadata.Builder indexMetadataBuilder = IndexMetadata.builder(indexMetadata);
indexMetadataBuilder.version(indexMetadataBuilder.version() + 1);
indexMetadataBuilder.putCustom(ILM_CUSTOM_METADATA_KEY, lifecycleState.asMap());
// drop it into the indices
final ImmutableOpenMap.Builder builder = ImmutableOpenMap.builder(indices);
builder.put(index.getName(), indexMetadataBuilder.build());
// construct a new Metadata object directly rather than using Metadata.builder(this).[...].build().
// the Metadata.Builder validation needs to handle the general case where anything at all could
// have changed, and hence it is expensive -- since we are changing so little about the metadata
// (and at a leaf in the object tree), we can bypass that validation for efficiency's sake
return new Metadata(
clusterUUID,
clusterUUIDCommitted,
version,
coordinationMetadata,
transientSettings,
persistentSettings,
settings,
hashesOfConsistentSettings,
totalNumberOfShards,
totalOpenIndexShards,
builder.build(),
aliasedIndices,
templates,
customs,
allIndices,
visibleIndices,
allOpenIndices,
visibleOpenIndices,
allClosedIndices,
visibleClosedIndices,
indicesLookup,
mappingsByHash,
oldestIndexVersion,
reservedStateMetadata
);
}
public Metadata withIndexSettingsUpdates(final Map updates) {
Objects.requireNonNull(updates, "no indices to update settings for");
final ImmutableOpenMap.Builder builder = ImmutableOpenMap.builder(indices);
updates.forEach((index, settings) -> {
IndexMetadata previous = builder.remove(index.getName());
assert previous != null : index;
builder.put(
index.getName(),
IndexMetadata.builder(previous).settingsVersion(previous.getSettingsVersion() + 1L).settings(settings).build()
);
});
return new Metadata(
clusterUUID,
clusterUUIDCommitted,
version,
coordinationMetadata,
transientSettings,
persistentSettings,
settings,
hashesOfConsistentSettings,
totalNumberOfShards,
totalOpenIndexShards,
builder.build(),
aliasedIndices,
templates,
customs,
allIndices,
visibleIndices,
allOpenIndices,
visibleOpenIndices,
allClosedIndices,
visibleClosedIndices,
indicesLookup,
mappingsByHash,
oldestIndexVersion,
reservedStateMetadata
);
}
public Metadata withCoordinationMetadata(CoordinationMetadata coordinationMetadata) {
return new Metadata(
clusterUUID,
clusterUUIDCommitted,
version,
coordinationMetadata,
transientSettings,
persistentSettings,
settings,
hashesOfConsistentSettings,
totalNumberOfShards,
totalOpenIndexShards,
indices,
aliasedIndices,
templates,
customs,
allIndices,
visibleIndices,
allOpenIndices,
visibleOpenIndices,
allClosedIndices,
visibleClosedIndices,
indicesLookup,
mappingsByHash,
oldestIndexVersion,
reservedStateMetadata
);
}
public Metadata withLastCommittedValues(
boolean clusterUUIDCommitted,
CoordinationMetadata.VotingConfiguration lastCommittedConfiguration
) {
if (clusterUUIDCommitted == this.clusterUUIDCommitted
&& lastCommittedConfiguration.equals(this.coordinationMetadata.getLastCommittedConfiguration())) {
return this;
}
return new Metadata(
clusterUUID,
clusterUUIDCommitted,
version,
CoordinationMetadata.builder(coordinationMetadata).lastCommittedConfiguration(lastCommittedConfiguration).build(),
transientSettings,
persistentSettings,
settings,
hashesOfConsistentSettings,
totalNumberOfShards,
totalOpenIndexShards,
indices,
aliasedIndices,
templates,
customs,
allIndices,
visibleIndices,
allOpenIndices,
visibleOpenIndices,
allClosedIndices,
visibleClosedIndices,
indicesLookup,
mappingsByHash,
oldestIndexVersion,
reservedStateMetadata
);
}
/**
* Creates a copy of this instance updated with the given {@link IndexMetadata} that must only contain changes to primary terms
* and in-sync allocation ids relative to the existing entries. This method is only used by
* {@link org.elasticsearch.cluster.routing.allocation.IndexMetadataUpdater#applyChanges(Metadata, RoutingTable)}.
* @param updates map of index name to {@link IndexMetadata}.
* @return updated metadata instance
*/
public Metadata withAllocationAndTermUpdatesOnly(Map updates) {
if (updates.isEmpty()) {
return this;
}
final var updatedIndicesBuilder = ImmutableOpenMap.builder(indices);
updatedIndicesBuilder.putAllFromMap(updates);
return new Metadata(
clusterUUID,
clusterUUIDCommitted,
version,
coordinationMetadata,
transientSettings,
persistentSettings,
settings,
hashesOfConsistentSettings,
totalNumberOfShards,
totalOpenIndexShards,
updatedIndicesBuilder.build(),
aliasedIndices,
templates,
customs,
allIndices,
visibleIndices,
allOpenIndices,
visibleOpenIndices,
allClosedIndices,
visibleClosedIndices,
indicesLookup,
mappingsByHash,
oldestIndexVersion,
reservedStateMetadata
);
}
/**
* Creates a copy of this instance with the given {@code index} added.
* @param index index to add
* @return copy with added index
*/
public Metadata withAddedIndex(IndexMetadata index) {
final String indexName = index.getIndex().getName();
ensureNoNameCollision(indexName);
final Map aliases = index.getAliases();
final ImmutableOpenMap> updatedAliases = aliasesAfterAddingIndex(index, aliases);
final String[] updatedVisibleIndices;
if (index.isHidden()) {
updatedVisibleIndices = visibleIndices;
} else {
updatedVisibleIndices = ArrayUtils.append(visibleIndices, indexName);
}
final String[] updatedAllIndices = ArrayUtils.append(allIndices, indexName);
final String[] updatedOpenIndices;
final String[] updatedClosedIndices;
final String[] updatedVisibleOpenIndices;
final String[] updatedVisibleClosedIndices;
switch (index.getState()) {
case OPEN -> {
updatedOpenIndices = ArrayUtils.append(allOpenIndices, indexName);
if (index.isHidden() == false) {
updatedVisibleOpenIndices = ArrayUtils.append(visibleOpenIndices, indexName);
} else {
updatedVisibleOpenIndices = visibleOpenIndices;
}
updatedVisibleClosedIndices = visibleClosedIndices;
updatedClosedIndices = allClosedIndices;
}
case CLOSE -> {
updatedOpenIndices = allOpenIndices;
updatedClosedIndices = ArrayUtils.append(allClosedIndices, indexName);
updatedVisibleOpenIndices = visibleOpenIndices;
if (index.isHidden() == false) {
updatedVisibleClosedIndices = ArrayUtils.append(visibleClosedIndices, indexName);
} else {
updatedVisibleClosedIndices = visibleClosedIndices;
}
}
default -> throw new AssertionError("impossible, index is either open or closed");
}
final MappingMetadata mappingMetadata = index.mapping();
final Map updatedMappingsByHash;
if (mappingMetadata == null) {
updatedMappingsByHash = mappingsByHash;
} else {
final MappingMetadata existingMapping = mappingsByHash.get(mappingMetadata.getSha256());
if (existingMapping != null) {
index = index.withMappingMetadata(existingMapping);
updatedMappingsByHash = mappingsByHash;
} else {
updatedMappingsByHash = Maps.copyMapWithAddedEntry(mappingsByHash, mappingMetadata.getSha256(), mappingMetadata);
}
}
final ImmutableOpenMap.Builder builder = ImmutableOpenMap.builder(indices);
builder.put(indexName, index);
final ImmutableOpenMap indicesMap = builder.build();
for (var entry : updatedAliases.entrySet()) {
List aliasIndices = entry.getValue().stream().map(idx -> indicesMap.get(idx.getName())).toList();
Builder.validateAlias(entry.getKey(), aliasIndices);
}
return new Metadata(
clusterUUID,
clusterUUIDCommitted,
version,
coordinationMetadata,
transientSettings,
persistentSettings,
settings,
hashesOfConsistentSettings,
totalNumberOfShards + index.getTotalNumberOfShards(),
totalOpenIndexShards + (index.getState() == IndexMetadata.State.OPEN ? index.getTotalNumberOfShards() : 0),
indicesMap,
updatedAliases,
templates,
customs,
updatedAllIndices,
updatedVisibleIndices,
updatedOpenIndices,
updatedVisibleOpenIndices,
updatedClosedIndices,
updatedVisibleClosedIndices,
null,
updatedMappingsByHash,
IndexVersion.min(IndexVersion.fromId(index.getCompatibilityVersion().id), oldestIndexVersion),
reservedStateMetadata
);
}
private ImmutableOpenMap> aliasesAfterAddingIndex(IndexMetadata index, Map aliases) {
if (aliases.isEmpty()) {
return aliasedIndices;
}
final String indexName = index.getIndex().getName();
final ImmutableOpenMap.Builder> aliasesBuilder = ImmutableOpenMap.builder(aliasedIndices);
for (String alias : aliases.keySet()) {
ensureNoNameCollision(alias);
if (aliasedIndices.containsKey(indexName)) {
throw new IllegalArgumentException("alias with name [" + indexName + "] already exists");
}
final Set found = aliasesBuilder.get(alias);
final Set updated;
if (found == null) {
updated = Set.of(index.getIndex());
} else {
final Set tmp = new HashSet<>(found);
tmp.add(index.getIndex());
updated = Set.copyOf(tmp);
}
aliasesBuilder.put(alias, updated);
}
return aliasesBuilder.build();
}
private void ensureNoNameCollision(String indexName) {
if (indices.containsKey(indexName)) {
throw new IllegalArgumentException("index with name [" + indexName + "] already exists");
}
if (dataStreams().containsKey(indexName)) {
throw new IllegalArgumentException("data stream with name [" + indexName + "] already exists");
}
if (dataStreamAliases().containsKey(indexName)) {
throw new IllegalStateException("data stream alias and indices alias have the same name (" + indexName + ")");
}
}
public long version() {
return this.version;
}
public String clusterUUID() {
return this.clusterUUID;
}
/**
* Whether the current node with the given cluster state is locked into the cluster with the UUID returned by {@link #clusterUUID()},
* meaning that it will not accept any cluster state with a different clusterUUID.
*/
public boolean clusterUUIDCommitted() {
return this.clusterUUIDCommitted;
}
/**
* Returns the merged transient and persistent settings.
*/
public Settings settings() {
return this.settings;
}
public Settings transientSettings() {
return this.transientSettings;
}
public Settings persistentSettings() {
return this.persistentSettings;
}
public Map hashesOfConsistentSettings() {
return this.hashesOfConsistentSettings;
}
public CoordinationMetadata coordinationMetadata() {
return this.coordinationMetadata;
}
public IndexVersion oldestIndexVersion() {
return this.oldestIndexVersion;
}
public boolean equalsAliases(Metadata other) {
for (IndexMetadata otherIndex : other.indices().values()) {
IndexMetadata thisIndex = index(otherIndex.getIndex());
if (thisIndex == null) {
return false;
}
if (otherIndex.getAliases().equals(thisIndex.getAliases()) == false) {
return false;
}
}
if (other.dataStreamAliases().size() != dataStreamAliases().size()) {
return false;
}
for (DataStreamAlias otherAlias : other.dataStreamAliases().values()) {
DataStreamAlias thisAlias = dataStreamAliases().get(otherAlias.getName());
if (thisAlias == null) {
return false;
}
if (thisAlias.equals(otherAlias) == false) {
return false;
}
}
return true;
}
public boolean indicesLookupInitialized() {
return indicesLookup != null;
}
public SortedMap getIndicesLookup() {
SortedMap lookup = indicesLookup;
if (lookup == null) {
lookup = buildIndicesLookup();
}
return lookup;
}
private synchronized SortedMap buildIndicesLookup() {
SortedMap i = indicesLookup;
if (i != null) {
return i;
}
i = Builder.buildIndicesLookup(custom(DataStreamMetadata.TYPE, DataStreamMetadata.EMPTY), indices);
indicesLookup = i;
return i;
}
public boolean sameIndicesLookup(Metadata other) {
return this.indicesLookup == other.indicesLookup;
}
/**
* Finds the specific index aliases that point to the requested concrete indices directly
* or that match with the indices via wildcards.
*
* @param concreteIndices The concrete indices that the aliases must point to in order to be returned.
* @return A map of index name to the list of aliases metadata. If a concrete index does not have matching
* aliases then the result will not include the index's key.
*/
public Map> findAllAliases(final String[] concreteIndices) {
return findAliases(Strings.EMPTY_ARRAY, concreteIndices);
}
/**
* Finds the specific index aliases that match with the specified aliases directly or partially via wildcards, and
* that point to the specified concrete indices (directly or matching indices via wildcards).
*
* @param aliases The aliases to look for. Might contain include or exclude wildcards.
* @param concreteIndices The concrete indices that the aliases must point to in order to be returned
* @return A map of index name to the list of aliases metadata. If a concrete index does not have matching
* aliases then the result will not include the index's key.
*/
public Map> findAliases(final String[] aliases, final String[] concreteIndices) {
assert aliases != null;
assert concreteIndices != null;
if (concreteIndices.length == 0) {
return ImmutableOpenMap.of();
}
String[] patterns = new String[aliases.length];
boolean[] include = new boolean[aliases.length];
for (int i = 0; i < aliases.length; i++) {
String alias = aliases[i];
if (alias.charAt(0) == '-') {
patterns[i] = alias.substring(1);
include[i] = false;
} else {
patterns[i] = alias;
include[i] = true;
}
}
boolean matchAllAliases = patterns.length == 0;
ImmutableOpenMap.Builder> mapBuilder = ImmutableOpenMap.builder();
for (String index : concreteIndices) {
IndexMetadata indexMetadata = indices.get(index);
List filteredValues = new ArrayList<>();
for (AliasMetadata aliasMetadata : indexMetadata.getAliases().values()) {
boolean matched = matchAllAliases;
String alias = aliasMetadata.alias();
for (int i = 0; i < patterns.length; i++) {
if (include[i]) {
if (matched == false) {
String pattern = patterns[i];
matched = ALL.equals(pattern) || Regex.simpleMatch(pattern, alias);
}
} else if (matched) {
matched = Regex.simpleMatch(patterns[i], alias) == false;
}
}
if (matched) {
filteredValues.add(aliasMetadata);
}
}
if (filteredValues.isEmpty() == false) {
// Make the list order deterministic
CollectionUtil.timSort(filteredValues, Comparator.comparing(AliasMetadata::alias));
mapBuilder.put(index, Collections.unmodifiableList(filteredValues));
}
}
return mapBuilder.build();
}
/**
* Finds all mappings for concrete indices. Only fields that match the provided field
* filter will be returned (default is a predicate that always returns true, which can be
* overridden via plugins)
*
* @see MapperPlugin#getFieldFilter()
*
* @param onNextIndex a hook that gets notified for each index that's processed
*/
public Map findMappings(
String[] concreteIndices,
Function> fieldFilter,
Runnable onNextIndex
) {
assert Transports.assertNotTransportThread("decompressing mappings is too expensive for a transport thread");
assert concreteIndices != null;
if (concreteIndices.length == 0) {
return ImmutableOpenMap.of();
}
ImmutableOpenMap.Builder indexMapBuilder = ImmutableOpenMap.builder();
Set indicesKeys = indices.keySet();
Stream.of(concreteIndices).filter(indicesKeys::contains).forEach(index -> {
onNextIndex.run();
IndexMetadata indexMetadata = indices.get(index);
Predicate fieldPredicate = fieldFilter.apply(index);
indexMapBuilder.put(index, filterFields(indexMetadata.mapping(), fieldPredicate));
});
return indexMapBuilder.build();
}
/**
* Finds the parent data streams, if any, for the specified concrete indices.
*/
public Map findDataStreams(String... concreteIndices) {
assert concreteIndices != null;
final ImmutableOpenMap.Builder builder = ImmutableOpenMap.builder();
final SortedMap lookup = getIndicesLookup();
for (String indexName : concreteIndices) {
IndexAbstraction index = lookup.get(indexName);
assert index != null;
assert index.getType() == IndexAbstraction.Type.CONCRETE_INDEX;
if (index.getParentDataStream() != null) {
builder.put(indexName, index.getParentDataStream());
}
}
return builder.build();
}
@SuppressWarnings("unchecked")
private static MappingMetadata filterFields(MappingMetadata mappingMetadata, Predicate fieldPredicate) {
if (mappingMetadata == null) {
return MappingMetadata.EMPTY_MAPPINGS;
}
if (fieldPredicate == MapperPlugin.NOOP_FIELD_PREDICATE) {
return mappingMetadata;
}
Map sourceAsMap = XContentHelper.convertToMap(mappingMetadata.source().compressedReference(), true).v2();
Map mapping;
if (sourceAsMap.size() == 1 && sourceAsMap.containsKey(mappingMetadata.type())) {
mapping = (Map) sourceAsMap.get(mappingMetadata.type());
} else {
mapping = sourceAsMap;
}
Map properties = (Map) mapping.get("properties");
if (properties == null || properties.isEmpty()) {
return mappingMetadata;
}
filterFields("", properties, fieldPredicate);
return new MappingMetadata(mappingMetadata.type(), sourceAsMap);
}
@SuppressWarnings("unchecked")
private static boolean filterFields(String currentPath, Map fields, Predicate fieldPredicate) {
assert fieldPredicate != MapperPlugin.NOOP_FIELD_PREDICATE;
Iterator> entryIterator = fields.entrySet().iterator();
while (entryIterator.hasNext()) {
Map.Entry entry = entryIterator.next();
String newPath = mergePaths(currentPath, entry.getKey());
Object value = entry.getValue();
boolean mayRemove = true;
boolean isMultiField = false;
if (value instanceof Map) {
Map map = (Map) value;
Map properties = (Map) map.get("properties");
if (properties != null) {
mayRemove = filterFields(newPath, properties, fieldPredicate);
} else {
Map subFields = (Map) map.get("fields");
if (subFields != null) {
isMultiField = true;
if (mayRemove = filterFields(newPath, subFields, fieldPredicate)) {
map.remove("fields");
}
}
}
} else {
throw new IllegalStateException("cannot filter mappings, found unknown element of type [" + value.getClass() + "]");
}
// only remove a field if it has no sub-fields left and it has to be excluded
if (fieldPredicate.test(newPath) == false) {
if (mayRemove) {
entryIterator.remove();
} else if (isMultiField) {
// multi fields that should be excluded but hold subfields that don't have to be excluded are converted to objects
Map map = (Map) value;
Map subFields = (Map) map.get("fields");
assert subFields.size() > 0;
map.put("properties", subFields);
map.remove("fields");
map.remove("type");
}
}
}
// return true if the ancestor may be removed, as it has no sub-fields left
return fields.size() == 0;
}
private static String mergePaths(String path, String field) {
if (path.length() == 0) {
return field;
}
return path + "." + field;
}
/**
* Returns all the concrete indices.
*/
public String[] getConcreteAllIndices() {
return allIndices;
}
/**
* Returns all the concrete indices that are not hidden.
*/
public String[] getConcreteVisibleIndices() {
return visibleIndices;
}
/**
* Returns all of the concrete indices that are open.
*/
public String[] getConcreteAllOpenIndices() {
return allOpenIndices;
}
/**
* Returns all of the concrete indices that are open and not hidden.
*/
public String[] getConcreteVisibleOpenIndices() {
return visibleOpenIndices;
}
/**
* Returns all of the concrete indices that are closed.
*/
public String[] getConcreteAllClosedIndices() {
return allClosedIndices;
}
/**
* Returns all of the concrete indices that are closed and not hidden.
*/
public String[] getConcreteVisibleClosedIndices() {
return visibleClosedIndices;
}
/**
* Returns indexing routing for the given aliasOrIndex
. Resolves routing from the alias metadata used
* in the write index.
*/
public String resolveWriteIndexRouting(@Nullable String routing, String aliasOrIndex) {
if (aliasOrIndex == null) {
return routing;
}
IndexAbstraction result = getIndicesLookup().get(aliasOrIndex);
if (result == null || result.getType() != IndexAbstraction.Type.ALIAS) {
return routing;
}
Index writeIndexName = result.getWriteIndex();
if (writeIndexName == null) {
throw new IllegalArgumentException("alias [" + aliasOrIndex + "] does not have a write index");
}
AliasMetadata writeIndexAliasMetadata = index(writeIndexName).getAliases().get(result.getName());
if (writeIndexAliasMetadata != null) {
return resolveRouting(routing, aliasOrIndex, writeIndexAliasMetadata);
} else {
return routing;
}
}
/**
* Returns indexing routing for the given index.
*/
// TODO: This can be moved to IndexNameExpressionResolver too, but this means that we will support wildcards and other expressions
// in the index,bulk,update and delete apis.
public String resolveIndexRouting(@Nullable String routing, String aliasOrIndex) {
if (aliasOrIndex == null) {
return routing;
}
IndexAbstraction result = getIndicesLookup().get(aliasOrIndex);
if (result == null || result.getType() != IndexAbstraction.Type.ALIAS) {
return routing;
}
if (result.getIndices().size() > 1) {
rejectSingleIndexOperation(aliasOrIndex, result);
}
return resolveRouting(routing, aliasOrIndex, AliasMetadata.getFirstAliasMetadata(this, result));
}
private static String resolveRouting(@Nullable String routing, String aliasOrIndex, AliasMetadata aliasMd) {
if (aliasMd.indexRouting() != null) {
if (aliasMd.indexRouting().indexOf(',') != -1) {
throw new IllegalArgumentException(
"index/alias ["
+ aliasOrIndex
+ "] provided with routing value ["
+ aliasMd.getIndexRouting()
+ "] that resolved to several routing values, rejecting operation"
);
}
if (routing != null) {
if (routing.equals(aliasMd.indexRouting()) == false) {
throw new IllegalArgumentException(
"Alias ["
+ aliasOrIndex
+ "] has index routing associated with it ["
+ aliasMd.indexRouting()
+ "], and was provided with routing value ["
+ routing
+ "], rejecting operation"
);
}
}
// Alias routing overrides the parent routing (if any).
return aliasMd.indexRouting();
}
return routing;
}
private static void rejectSingleIndexOperation(String aliasOrIndex, IndexAbstraction result) {
String[] indexNames = new String[result.getIndices().size()];
int i = 0;
for (Index indexName : result.getIndices()) {
indexNames[i++] = indexName.getName();
}
throw new IllegalArgumentException(
"Alias ["
+ aliasOrIndex
+ "] has more than one index associated with it ["
+ Arrays.toString(indexNames)
+ "], can't execute a single index op"
);
}
/**
* Checks whether an index exists (as of this {@link Metadata} with the given name. Does not check aliases or data streams.
* @param index An index name that may or may not exist in the cluster.
* @return {@code true} if a concrete index with that name exists, {@code false} otherwise.
*/
public boolean hasIndex(String index) {
return indices.containsKey(index);
}
/**
* Checks whether an index exists. Similar to {@link Metadata#hasIndex(String)}, but ensures that the index has the same UUID as
* the given {@link Index}.
* @param index An {@link Index} object that may or may not exist in the cluster.
* @return {@code true} if an index exists with the same name and UUID as the given index object, {@code false} otherwise.
*/
public boolean hasIndex(Index index) {
IndexMetadata metadata = index(index.getName());
return metadata != null && metadata.getIndexUUID().equals(index.getUUID());
}
/**
* Checks whether an index abstraction (that is, index, alias, or data stream) exists (as of this {@link Metadata} with the given name.
* @param index An index name that may or may not exist in the cluster.
* @return {@code true} if an index abstraction with that name exists, {@code false} otherwise.
*/
public boolean hasIndexAbstraction(String index) {
return getIndicesLookup().containsKey(index);
}
public IndexMetadata index(String index) {
return indices.get(index);
}
public IndexMetadata index(Index index) {
IndexMetadata metadata = index(index.getName());
if (metadata != null && metadata.getIndexUUID().equals(index.getUUID())) {
return metadata;
}
return null;
}
/** Returns true iff existing index has the same {@link IndexMetadata} instance */
public boolean hasIndexMetadata(final IndexMetadata indexMetadata) {
return indices.get(indexMetadata.getIndex().getName()) == indexMetadata;
}
/**
* Returns the {@link IndexMetadata} for this index.
* @throws IndexNotFoundException if no metadata for this index is found
*/
public IndexMetadata getIndexSafe(Index index) {
IndexMetadata metadata = index(index.getName());
if (metadata != null) {
if (metadata.getIndexUUID().equals(index.getUUID())) {
return metadata;
}
throw new IndexNotFoundException(
index,
new IllegalStateException(
"index uuid doesn't match expected: [" + index.getUUID() + "] but got: [" + metadata.getIndexUUID() + "]"
)
);
}
throw new IndexNotFoundException(index);
}
public Map indices() {
return this.indices;
}
public Map getIndices() {
return indices();
}
/**
* Returns whether an alias exists with provided alias name.
*
* @param aliasName The provided alias name
* @return whether an alias exists with provided alias name
*/
public boolean hasAlias(String aliasName) {
return aliasedIndices.containsKey(aliasName) || dataStreamAliases().containsKey(aliasName);
}
/**
* Returns all the indices that the alias with the provided alias name refers to.
* These are aliased indices. Not that, this only return indices that have been aliased
* and not indices that are behind a data stream or data stream alias.
*
* @param aliasName The provided alias name
* @return all aliased indices by the alias with the provided alias name
*/
public Set aliasedIndices(String aliasName) {
Objects.requireNonNull(aliasName);
return aliasedIndices.getOrDefault(aliasName, Set.of());
}
/**
* @return the names of all indices aliases.
*/
public Set aliasedIndices() {
return aliasedIndices.keySet();
}
public Map templates() {
return this.templates;
}
public Map getTemplates() {
return templates();
}
public Map componentTemplates() {
return Optional.ofNullable((ComponentTemplateMetadata) this.custom(ComponentTemplateMetadata.TYPE))
.map(ComponentTemplateMetadata::componentTemplates)
.orElse(Collections.emptyMap());
}
public Map templatesV2() {
return Optional.ofNullable((ComposableIndexTemplateMetadata) this.custom(ComposableIndexTemplateMetadata.TYPE))
.map(ComposableIndexTemplateMetadata::indexTemplates)
.orElse(Collections.emptyMap());
}
public boolean isTimeSeriesTemplate(ComposableIndexTemplate indexTemplate) {
var template = indexTemplate.template();
if (indexTemplate.getDataStreamTemplate() == null || template == null) {
return false;
}
var settings = MetadataIndexTemplateService.resolveSettings(indexTemplate, componentTemplates());
// Not using IndexSettings.MODE.get() to avoid validation that may fail at this point.
var rawIndexMode = settings.get(IndexSettings.MODE.getKey());
var indexMode = rawIndexMode != null ? Enum.valueOf(IndexMode.class, rawIndexMode.toUpperCase(Locale.ROOT)) : null;
if (indexMode == IndexMode.TIME_SERIES) {
// No need to check for the existence of index.routing_path here, because index.mode=time_series can't be specified without it.
// Setting validation takes care of this.
// Also no need to validate that the fields defined in index.routing_path are keyword fields with time_series_dimension
// attribute enabled. This is validated elsewhere (DocumentMapper).
return true;
}
// in a followup change: check the existence of keyword fields of type keyword and time_series_dimension attribute enabled in
// the template. In this case the index.routing_path setting can be generated from the mapping.
return false;
}
public Map dataStreams() {
return this.custom(DataStreamMetadata.TYPE, DataStreamMetadata.EMPTY).dataStreams();
}
public Map dataStreamAliases() {
return this.custom(DataStreamMetadata.TYPE, DataStreamMetadata.EMPTY).getDataStreamAliases();
}
public NodesShutdownMetadata nodeShutdowns() {
return custom(NodesShutdownMetadata.TYPE, NodesShutdownMetadata.EMPTY);
}
/**
* Indicates if the provided index is managed by ILM. This takes into account if the index is part of
* data stream that's potentially managed by DLM and the value of the {@link org.elasticsearch.index.IndexSettings#PREFER_ILM_SETTING}
*/
public boolean isIndexManagedByILM(IndexMetadata indexMetadata) {
if (Strings.hasText(indexMetadata.getLifecyclePolicyName()) == false) {
// no ILM policy configured so short circuit this to *not* managed by ILM
return false;
}
IndexAbstraction indexAbstraction = getIndicesLookup().get(indexMetadata.getIndex().getName());
if (indexAbstraction == null) {
// index doesn't exist anymore
return false;
}
DataStream parentDataStream = indexAbstraction.getParentDataStream();
if (parentDataStream != null && parentDataStream.getLifecycle() != null) {
// index has both ILM and DLM configured so let's check which is preferred
return PREFER_ILM_SETTING.get(indexMetadata.getSettings());
}
return true;
}
public Map customs() {
return this.customs;
}
/**
* Returns the full {@link ReservedStateMetadata} Map for all
* reserved state namespaces.
* @return a map of namespace to {@link ReservedStateMetadata}
*/
public Map reservedStateMetadata() {
return this.reservedStateMetadata;
}
/**
* The collection of index deletions in the cluster.
*/
public IndexGraveyard indexGraveyard() {
return custom(IndexGraveyard.TYPE);
}
@SuppressWarnings("unchecked")
public T custom(String type) {
return (T) customs.get(type);
}
@SuppressWarnings("unchecked")
public T custom(String type, T defaultValue) {
return (T) customs.getOrDefault(type, defaultValue);
}
/**
* Gets the total number of shards from all indices, including replicas and
* closed indices.
* @return The total number shards from all indices.
*/
public int getTotalNumberOfShards() {
return this.totalNumberOfShards;
}
/**
* Gets the total number of open shards from all indices. Includes
* replicas, but does not include shards that are part of closed indices.
* @return The total number of open shards from all indices.
*/
public int getTotalOpenIndexShards() {
return this.totalOpenIndexShards;
}
@Override
public Iterator iterator() {
return indices.values().iterator();
}
public Stream stream() {
return indices.values().stream();
}
public int size() {
return indices.size();
}
public static boolean isGlobalStateEquals(Metadata metadata1, Metadata metadata2) {
if (metadata1.coordinationMetadata.equals(metadata2.coordinationMetadata) == false) {
return false;
}
if (metadata1.persistentSettings.equals(metadata2.persistentSettings) == false) {
return false;
}
if (metadata1.hashesOfConsistentSettings.equals(metadata2.hashesOfConsistentSettings) == false) {
return false;
}
if (metadata1.templates.equals(metadata2.templates()) == false) {
return false;
}
if (metadata1.clusterUUID.equals(metadata2.clusterUUID) == false) {
return false;
}
if (metadata1.clusterUUIDCommitted != metadata2.clusterUUIDCommitted) {
return false;
}
// Check if any persistent metadata needs to be saved
int customCount1 = 0;
for (Map.Entry cursor : metadata1.customs.entrySet()) {
if (cursor.getValue().context().contains(XContentContext.GATEWAY)) {
if (cursor.getValue().equals(metadata2.custom(cursor.getKey())) == false) {
return false;
}
customCount1++;
}
}
int customCount2 = 0;
for (Custom custom : metadata2.customs.values()) {
if (custom.context().contains(XContentContext.GATEWAY)) {
customCount2++;
}
}
if (customCount1 != customCount2) {
return false;
}
if (Objects.equals(metadata1.reservedStateMetadata, metadata2.reservedStateMetadata) == false) {
return false;
}
return true;
}
@Override
public Diff diff(Metadata previousState) {
return new MetadataDiff(previousState, this);
}
public static Diff readDiffFrom(StreamInput in) throws IOException {
if (in.getTransportVersion().onOrAfter(MetadataDiff.NOOP_METADATA_DIFF_VERSION) && in.readBoolean()) {
return SimpleDiffable.empty();
}
return new MetadataDiff(in);
}
public static Metadata fromXContent(XContentParser parser) throws IOException {
return Builder.fromXContent(parser);
}
@Override
public Iterator extends ToXContent> toXContentChunked(ToXContent.Params p) {
XContentContext context = XContentContext.valueOf(p.param(CONTEXT_MODE_PARAM, CONTEXT_MODE_API));
final Iterator extends ToXContent> start = context == XContentContext.API
? ChunkedToXContentHelper.startObject("metadata")
: Iterators.single((builder, params) -> builder.startObject("meta-data").field("version", version()));
final Iterator extends ToXContent> persistentSettings = context != XContentContext.API && persistentSettings().isEmpty() == false
? Iterators.single((builder, params) -> {
builder.startObject("settings");
persistentSettings().toXContent(builder, new ToXContent.MapParams(Collections.singletonMap("flat_settings", "true")));
return builder.endObject();
})
: Collections.emptyIterator();
final Iterator extends ToXContent> indices = context == XContentContext.API
? ChunkedToXContentHelper.wrapWithObject("indices", indices().values().iterator())
: Collections.emptyIterator();
return Iterators.concat(start, Iterators.single((builder, params) -> {
builder.field("cluster_uuid", clusterUUID);
builder.field("cluster_uuid_committed", clusterUUIDCommitted);
builder.startObject("cluster_coordination");
coordinationMetadata().toXContent(builder, params);
return builder.endObject();
}),
persistentSettings,
ChunkedToXContentHelper.wrapWithObject(
"templates",
templates().values()
.stream()
.map(
template -> (ToXContent) (builder, params) -> IndexTemplateMetadata.Builder.toXContentWithTypes(
template,
builder,
params
)
)
.iterator()
),
indices,
Iterators.flatMap(
customs.entrySet().iterator(),
entry -> entry.getValue().context().contains(context)
? ChunkedToXContentHelper.wrapWithObject(entry.getKey(), entry.getValue().toXContentChunked(p))
: Collections.emptyIterator()
),
ChunkedToXContentHelper.wrapWithObject("reserved_state", reservedStateMetadata().values().iterator()),
ChunkedToXContentHelper.endObject()
);
}
public Map getMappingsByHash() {
return mappingsByHash;
}
private static class MetadataDiff implements Diff {
private static final TransportVersion NOOP_METADATA_DIFF_VERSION = TransportVersion.V_8_5_0;
private static final TransportVersion NOOP_METADATA_DIFF_SAFE_VERSION =
PublicationTransportHandler.INCLUDES_LAST_COMMITTED_DATA_VERSION;
private final long version;
private final String clusterUUID;
private final boolean clusterUUIDCommitted;
private final CoordinationMetadata coordinationMetadata;
private final Settings transientSettings;
private final Settings persistentSettings;
private final Diff hashesOfConsistentSettings;
private final Diff> indices;
private final Diff> templates;
private final Diff> customs;
private final Diff
© 2015 - 2025 Weber Informatics LLC | Privacy Policy