|
@@ -19,14 +19,17 @@ import org.elasticsearch.common.Strings;
|
|
|
import org.elasticsearch.common.settings.Settings;
|
|
|
import org.elasticsearch.common.xcontent.XContentBuilder;
|
|
|
import org.elasticsearch.common.xcontent.XContentHelper;
|
|
|
+import org.elasticsearch.common.xcontent.XContentType;
|
|
|
import org.elasticsearch.common.xcontent.json.JsonXContent;
|
|
|
|
|
|
import java.util.ArrayList;
|
|
|
import java.util.Collections;
|
|
|
+import java.util.HashSet;
|
|
|
import java.util.List;
|
|
|
import java.util.Locale;
|
|
|
import java.util.Map;
|
|
|
import java.util.Objects;
|
|
|
+import java.util.Set;
|
|
|
|
|
|
/**
|
|
|
* A system index descriptor describes one or more system indices. It can match a number of indices using
|
|
@@ -34,7 +37,7 @@ import java.util.Objects;
|
|
|
* indices that are managed internally to Elasticsearch, a descriptor can also include information for
|
|
|
* creating the system index, upgrading its mappings, and creating an alias.
|
|
|
*/
|
|
|
-public class SystemIndexDescriptor {
|
|
|
+public class SystemIndexDescriptor implements Comparable<SystemIndexDescriptor> {
|
|
|
/** A pattern, either with a wildcard or simple regex. Indices that match one of these patterns are considered system indices. */
|
|
|
private final String indexPattern;
|
|
|
|
|
@@ -71,9 +74,12 @@ public class SystemIndexDescriptor {
|
|
|
/** For internally-managed indices, specifies the origin to use when creating or updating the index */
|
|
|
private final String origin;
|
|
|
|
|
|
- /** The minimum cluster node version required for this descriptor, or null if there is no restriction */
|
|
|
+ /** The minimum cluster node version required for this descriptor */
|
|
|
private final Version minimumNodeVersion;
|
|
|
|
|
|
+ /** Mapping version from the descriptor */
|
|
|
+ private final Version mappingVersion;
|
|
|
+
|
|
|
/** Whether there are dynamic fields in this descriptor's mappings */
|
|
|
private final boolean hasDynamicMappings;
|
|
|
|
|
@@ -83,6 +89,12 @@ public class SystemIndexDescriptor {
|
|
|
/** A list of allowed product origins that may access an external system index */
|
|
|
private final List<String> allowedElasticProductOrigins;
|
|
|
|
|
|
+ /**
|
|
|
+ * A list of prior system index descriptors that can be used when one or more data/master nodes is on a version lower than the
|
|
|
+ * minimum supported version for this descriptor
|
|
|
+ */
|
|
|
+ private final List<SystemIndexDescriptor> priorSystemIndexDescriptors;
|
|
|
+
|
|
|
/**
|
|
|
* Creates a descriptor for system indices matching the supplied pattern. These indices will not be managed
|
|
|
* by Elasticsearch internally.
|
|
@@ -90,7 +102,8 @@ public class SystemIndexDescriptor {
|
|
|
* @param description The name of the plugin responsible for this system index.
|
|
|
*/
|
|
|
public SystemIndexDescriptor(String indexPattern, String description) {
|
|
|
- this(indexPattern, null, description, null, null, null, 0, null, null, null, Type.INTERNAL, List.of());
|
|
|
+ this(indexPattern, null, description, null, null, null, 0, null, null, Version.CURRENT.minimumCompatibilityVersion(),
|
|
|
+ Type.INTERNAL_UNMANAGED, List.of(), List.of());
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -103,7 +116,8 @@ public class SystemIndexDescriptor {
|
|
|
* indices
|
|
|
*/
|
|
|
public SystemIndexDescriptor(String indexPattern, String description, Type type, List<String> allowedElasticProductOrigins) {
|
|
|
- this(indexPattern, null, description, null, null, null, 0, null, null, null, type, allowedElasticProductOrigins);
|
|
|
+ this(indexPattern, null, description, null, null, null, 0, null, null, Version.CURRENT.minimumCompatibilityVersion(), type,
|
|
|
+ allowedElasticProductOrigins, List.of());
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -111,6 +125,7 @@ public class SystemIndexDescriptor {
|
|
|
* by Elasticsearch internally if mappings or settings are provided.
|
|
|
*
|
|
|
* @param indexPattern The pattern of index names that this descriptor will be used for. Must start with a '.' character.
|
|
|
+ * @param primaryIndex The primary index name of this descriptor. Used when creating the system index for the first time.
|
|
|
* @param description The name of the plugin responsible for this system index.
|
|
|
* @param mappings The mappings to apply to this index when auto-creating, if appropriate
|
|
|
* @param settings The settings to apply to this index when auto-creating, if appropriate
|
|
@@ -119,10 +134,12 @@ public class SystemIndexDescriptor {
|
|
|
* @param versionMetaKey a mapping key under <code>_meta</code> where a version can be found, which indicates the
|
|
|
* Elasticsearch version when the index was created.
|
|
|
* @param origin the client origin to use when creating this index.
|
|
|
- * @param minimumNodeVersion the minimum cluster node version required for this descriptor, or null if there is no restriction
|
|
|
+ * @param minimumNodeVersion the minimum cluster node version required for this descriptor
|
|
|
* @param type The {@link Type} of system index
|
|
|
* @param allowedElasticProductOrigins A list of allowed origin values that should be allowed access in the case of external system
|
|
|
* indices
|
|
|
+ * @param priorSystemIndexDescriptors A list of system index descriptors that describe the same index in a way that is compatible with
|
|
|
+ * older versions of Elasticsearch
|
|
|
*/
|
|
|
SystemIndexDescriptor(
|
|
|
String indexPattern,
|
|
@@ -136,7 +153,8 @@ public class SystemIndexDescriptor {
|
|
|
String origin,
|
|
|
Version minimumNodeVersion,
|
|
|
Type type,
|
|
|
- List<String> allowedElasticProductOrigins
|
|
|
+ List<String> allowedElasticProductOrigins,
|
|
|
+ List<SystemIndexDescriptor> priorSystemIndexDescriptors
|
|
|
) {
|
|
|
Objects.requireNonNull(indexPattern, "system index pattern must not be null");
|
|
|
if (indexPattern.length() < 2) {
|
|
@@ -176,12 +194,18 @@ public class SystemIndexDescriptor {
|
|
|
|
|
|
Strings.requireNonEmpty(indexPattern, "indexPattern must be supplied");
|
|
|
|
|
|
- if (mappings != null || settings != null) {
|
|
|
- Strings.requireNonEmpty(primaryIndex, "Must supply primaryIndex if mappings or settings are defined");
|
|
|
- Strings.requireNonEmpty(versionMetaKey, "Must supply versionMetaKey if mappings or settings are defined");
|
|
|
- Strings.requireNonEmpty(origin, "Must supply origin if mappings or settings are defined");
|
|
|
- }
|
|
|
Objects.requireNonNull(type, "type must not be null");
|
|
|
+ if (type.isManaged()) {
|
|
|
+ Objects.requireNonNull(settings, "Must supply settings for a managed system index");
|
|
|
+ Strings.requireNonEmpty(mappings, "Must supply mappings for a managed system index");
|
|
|
+ Strings.requireNonEmpty(primaryIndex, "Must supply primaryIndex for a managed system index");
|
|
|
+ Strings.requireNonEmpty(versionMetaKey, "Must supply versionMetaKey for a managed system index");
|
|
|
+ Strings.requireNonEmpty(origin, "Must supply origin for a managed system index");
|
|
|
+ this.mappingVersion = extractVersionFromMappings(mappings, versionMetaKey);;
|
|
|
+ } else {
|
|
|
+ this.mappingVersion = null;
|
|
|
+ }
|
|
|
+
|
|
|
Objects.requireNonNull(allowedElasticProductOrigins, "allowedProductOrigins must not be null");
|
|
|
if (type.isInternal() && allowedElasticProductOrigins.isEmpty() == false) {
|
|
|
throw new IllegalArgumentException("Allowed origins are not valid for internal system indices");
|
|
@@ -189,6 +213,40 @@ public class SystemIndexDescriptor {
|
|
|
throw new IllegalArgumentException("External system indices without allowed products is not a valid combination");
|
|
|
}
|
|
|
|
|
|
+ Objects.requireNonNull(minimumNodeVersion, "minimumNodeVersion must be provided!");
|
|
|
+ Objects.requireNonNull(priorSystemIndexDescriptors, "priorSystemIndexDescriptors must not be null");
|
|
|
+ if (priorSystemIndexDescriptors.isEmpty() == false) {
|
|
|
+ // the rules for prior system index descriptors
|
|
|
+ // 1. No values with the same minimum node version
|
|
|
+ // 2. All prior system index descriptors must have a minimumNodeVersion before this one
|
|
|
+ // 3. Prior system index descriptors may not have other prior system index descriptors
|
|
|
+ // to avoid multiple branches that need followed
|
|
|
+ // 4. Must have same indexPattern, primaryIndex, and alias
|
|
|
+ Set<Version> versions = new HashSet<>(priorSystemIndexDescriptors.size() + 1);
|
|
|
+ versions.add(minimumNodeVersion);
|
|
|
+ for (SystemIndexDescriptor prior : priorSystemIndexDescriptors) {
|
|
|
+ if (versions.add(prior.minimumNodeVersion) == false) {
|
|
|
+ throw new IllegalArgumentException(prior + " has the same minimum node version as another descriptor");
|
|
|
+ }
|
|
|
+ if (prior.minimumNodeVersion.after(minimumNodeVersion)) {
|
|
|
+ throw new IllegalArgumentException(prior + " has minimum node version [" + prior.minimumNodeVersion +
|
|
|
+ "] which is after [" + minimumNodeVersion + "]");
|
|
|
+ }
|
|
|
+ if (prior.priorSystemIndexDescriptors.isEmpty() == false) {
|
|
|
+ throw new IllegalArgumentException(prior + " has its own prior descriptors but only a depth of 1 is allowed");
|
|
|
+ }
|
|
|
+ if (prior.indexPattern.equals(indexPattern) == false) {
|
|
|
+ throw new IllegalArgumentException("index pattern must be the same");
|
|
|
+ }
|
|
|
+ if (prior.primaryIndex.equals(primaryIndex) == false) {
|
|
|
+ throw new IllegalArgumentException("primary index must be the same");
|
|
|
+ }
|
|
|
+ if (prior.aliasName.equals(aliasName) == false) {
|
|
|
+ throw new IllegalArgumentException("alias name must be the same");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
this.indexPattern = indexPattern;
|
|
|
this.primaryIndex = primaryIndex;
|
|
|
|
|
@@ -207,6 +265,16 @@ public class SystemIndexDescriptor {
|
|
|
this.allowedElasticProductOrigins = allowedElasticProductOrigins;
|
|
|
this.hasDynamicMappings = this.mappings != null
|
|
|
&& findDynamicMapping(XContentHelper.convertToMap(JsonXContent.jsonXContent, mappings, false));
|
|
|
+
|
|
|
+ final List<SystemIndexDescriptor> sortedPriorSystemIndexDescriptors;
|
|
|
+ if (priorSystemIndexDescriptors.isEmpty() || priorSystemIndexDescriptors.size() == 1) {
|
|
|
+ sortedPriorSystemIndexDescriptors = List.copyOf(priorSystemIndexDescriptors);
|
|
|
+ } else {
|
|
|
+ List<SystemIndexDescriptor> copy = new ArrayList<>(priorSystemIndexDescriptors);
|
|
|
+ Collections.sort(copy);
|
|
|
+ sortedPriorSystemIndexDescriptors = List.copyOf(copy);
|
|
|
+ }
|
|
|
+ this.priorSystemIndexDescriptors = sortedPriorSystemIndexDescriptors;
|
|
|
}
|
|
|
|
|
|
|
|
@@ -286,9 +354,12 @@ public class SystemIndexDescriptor {
|
|
|
return this.versionMetaKey;
|
|
|
}
|
|
|
|
|
|
+ public Version getMinimumNodeVersion() {
|
|
|
+ return minimumNodeVersion;
|
|
|
+ }
|
|
|
+
|
|
|
public boolean isAutomaticallyManaged() {
|
|
|
- // TODO remove mappings/settings check after all internal indices have been migrated
|
|
|
- return type.isManaged() && (this.mappings != null || this.settings != null);
|
|
|
+ return type.isManaged();
|
|
|
}
|
|
|
|
|
|
public String getOrigin() {
|
|
@@ -311,25 +382,49 @@ public class SystemIndexDescriptor {
|
|
|
return allowedElasticProductOrigins;
|
|
|
}
|
|
|
|
|
|
+ public Version getMappingVersion() {
|
|
|
+ if (type.isManaged() == false) {
|
|
|
+ throw new IllegalStateException(toString() + " is not managed so there are no mappings or version");
|
|
|
+ }
|
|
|
+ return mappingVersion;
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
- * Checks that this descriptor can be used within this cluster, by comparing the supplied minimum
|
|
|
- * node version to this descriptor's minimum version.
|
|
|
+ * Gets a standardized message when the node contains a data or master node whose version is less
|
|
|
+ * than that of the minimum supported version of this descriptor and its prior descriptors.
|
|
|
*
|
|
|
* @param cause the action being attempted that triggered the check. Used in the error message.
|
|
|
- * @param actualMinimumNodeVersion the lower node version in the cluster
|
|
|
- * @return an error message if the lowest node version is lower that the version in this descriptor,
|
|
|
- * or <code>null</code> if the supplied version is acceptable or this descriptor has no minimum version.
|
|
|
+ * @return the standardized error message
|
|
|
*/
|
|
|
- public String checkMinimumNodeVersion(String cause, Version actualMinimumNodeVersion) {
|
|
|
+ public String getMinimumNodeVersionMessage(String cause) {
|
|
|
Objects.requireNonNull(cause);
|
|
|
- if (this.minimumNodeVersion != null && this.minimumNodeVersion.after(actualMinimumNodeVersion)) {
|
|
|
- return String.format(
|
|
|
- Locale.ROOT,
|
|
|
- "[%s] failed - system index [%s] requires all cluster nodes to be at least version [%s]",
|
|
|
- cause,
|
|
|
- this.getPrimaryIndex(),
|
|
|
- minimumNodeVersion
|
|
|
- );
|
|
|
+ final Version actualMinimumVersion = priorSystemIndexDescriptors.isEmpty() ? minimumNodeVersion :
|
|
|
+ priorSystemIndexDescriptors.get(priorSystemIndexDescriptors.size() - 1).minimumNodeVersion;
|
|
|
+ return String.format(
|
|
|
+ Locale.ROOT,
|
|
|
+ "[%s] failed - system index [%s] requires all data and master nodes to be at least version [%s]",
|
|
|
+ cause,
|
|
|
+ this.getPrimaryIndex(),
|
|
|
+ actualMinimumVersion
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Finds the descriptor that can be used within this cluster, by comparing the supplied minimum
|
|
|
+ * node version to this descriptor's minimum version and the prior descriptors minimum version.
|
|
|
+ *
|
|
|
+ * @param version the lower node version in the cluster
|
|
|
+ * @return <code>null</code> if the lowest node version is lower than the minimum version in this descriptor,
|
|
|
+ * or the appropriate descriptor if the supplied version is acceptable.
|
|
|
+ */
|
|
|
+ public SystemIndexDescriptor getDescriptorCompatibleWith(Version version) {
|
|
|
+ if (minimumNodeVersion.onOrBefore(version)) {
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ for (SystemIndexDescriptor prior : priorSystemIndexDescriptors) {
|
|
|
+ if (version.onOrAfter(prior.minimumNodeVersion)) {
|
|
|
+ return prior;
|
|
|
+ }
|
|
|
}
|
|
|
return null;
|
|
|
}
|
|
@@ -338,20 +433,28 @@ public class SystemIndexDescriptor {
|
|
|
return new Builder();
|
|
|
}
|
|
|
|
|
|
+ @Override
|
|
|
+ public int compareTo(SystemIndexDescriptor other) {
|
|
|
+ return minimumNodeVersion.compareTo(other.minimumNodeVersion) * -1;
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
- * The specific type of system index that this descriptor represents. System indices have three defined types, which is used to
|
|
|
- * control behavior. Elasticsearch itself and plugins have system indices that are necessary for their features;
|
|
|
- * these system indices are referred to as internal system indices. Internal system indices are always managed indices that
|
|
|
- * Elasticsearch manages.
|
|
|
+ * The specific type of system index that this descriptor represents. System indices can be one of four defined types; the type is used
|
|
|
+ * to control behavior. Elasticsearch itself and plugins have system indices that are necessary for their features;
|
|
|
+ * these system indices are referred to as internal system indices. System indices can also belong to features outside of Elasticsearch
|
|
|
+ * that may be part of other Elastic stack components. These are external system indices as the intent is for these to be accessed via
|
|
|
+ * normal APIs with a special value.
|
|
|
+ *
|
|
|
+ * Within both internal and external system indices, there are two sub-types. The first are those that are managed by Elasticsearch and
|
|
|
+ * will have mappings/settings changed as the cluster itself is upgraded. The second are those managed by the owning applications code
|
|
|
+ * and for those Elasticsearch will not perform any updates.
|
|
|
*
|
|
|
- * System indices can also belong to features outside of Elasticsearch that may be part of other Elastic stack components. These are
|
|
|
- * external system indices as the intent is for these to be accessed via normal APIs with a special value. Within external system
|
|
|
- * indices, there are two sub-types. The first are those that are managed by Elasticsearch and will have mappings/settings changed as
|
|
|
- * the cluster itself is upgraded. The second are those managed by the external application and for those Elasticsearch will not
|
|
|
- * perform any updates.
|
|
|
+ * Internal system indices are almost always managed indices that Elasticsearch manages, but there are cases where the component of
|
|
|
+ * Elasticsearch will need to manage the system indices itself.
|
|
|
*/
|
|
|
public enum Type {
|
|
|
- INTERNAL(false, true),
|
|
|
+ INTERNAL_MANAGED(false, true),
|
|
|
+ INTERNAL_UNMANAGED(false, false),
|
|
|
EXTERNAL_MANAGED(true, true),
|
|
|
EXTERNAL_UNMANAGED(true, false);
|
|
|
|
|
@@ -389,9 +492,10 @@ public class SystemIndexDescriptor {
|
|
|
private int indexFormat = 0;
|
|
|
private String versionMetaKey = null;
|
|
|
private String origin = null;
|
|
|
- private Version minimumNodeVersion = null;
|
|
|
- private Type type = Type.INTERNAL;
|
|
|
+ private Version minimumNodeVersion = Version.CURRENT.minimumCompatibilityVersion();
|
|
|
+ private Type type = Type.INTERNAL_MANAGED;
|
|
|
private List<String> allowedElasticProductOrigins = List.of();
|
|
|
+ private List<SystemIndexDescriptor> priorSystemIndexDescriptors = List.of();
|
|
|
|
|
|
private Builder() {}
|
|
|
|
|
@@ -460,6 +564,11 @@ public class SystemIndexDescriptor {
|
|
|
return this;
|
|
|
}
|
|
|
|
|
|
+ public Builder setPriorSystemIndexDescriptors(List<SystemIndexDescriptor> priorSystemIndexDescriptors) {
|
|
|
+ this.priorSystemIndexDescriptors = priorSystemIndexDescriptors;
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* Builds a {@link SystemIndexDescriptor} using the fields supplied to this builder.
|
|
|
* @return a populated descriptor.
|
|
@@ -478,7 +587,8 @@ public class SystemIndexDescriptor {
|
|
|
origin,
|
|
|
minimumNodeVersion,
|
|
|
type,
|
|
|
- allowedElasticProductOrigins
|
|
|
+ allowedElasticProductOrigins,
|
|
|
+ priorSystemIndexDescriptors
|
|
|
);
|
|
|
}
|
|
|
}
|
|
@@ -546,4 +656,23 @@ public class SystemIndexDescriptor {
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
+
|
|
|
+ private static Version extractVersionFromMappings(String mappings, String versionMetaKey) {
|
|
|
+ final Map<String, Object> mappingsMap = XContentHelper.convertToMap(XContentType.JSON.xContent(), mappings, false);
|
|
|
+ final Map<String, Object> doc = (Map<String, Object>) mappingsMap.get("_doc");
|
|
|
+ final Map<String, Object> meta;
|
|
|
+ if (doc == null) {
|
|
|
+ meta = (Map<String, Object>) mappingsMap.get("_meta");
|
|
|
+ } else {
|
|
|
+ meta = (Map<String, Object>) doc.get("_meta");
|
|
|
+ }
|
|
|
+ if (meta == null) {
|
|
|
+ throw new IllegalStateException("mappings do not have _meta field");
|
|
|
+ }
|
|
|
+ final String value = (String) meta.get(versionMetaKey);
|
|
|
+ if (value == null) {
|
|
|
+ throw new IllegalArgumentException("mappings do not have a version in _meta." + versionMetaKey);
|
|
|
+ }
|
|
|
+ return Version.fromString(value);
|
|
|
+ }
|
|
|
}
|