Skip to content

Commit 86b8053

Browse files
committed
Migrate watcher to system indices infrastructure (elastic#67588)
Part of elastic#61656. Migrate the `.watches` and `.triggered_watches` system indices to use the auto-create infrastructure. The watcher history indices are left alone. As part of this work, a `SystemIndexDescriptor` now inspects its mappings to determine whether it has any dynamic mappings. This influences how strict Elasticsearch is with enforcing the descriptor's mappings, since ES cannot know in advanced what all the mappings will be. This PR also fixes the `SystemIndexManager` so that (1) it doesn't fall over when attempting to inspect the state of an index that hasn't been created yet, and (2) does update mappings if there's no version field in the mapping metadata.
1 parent 3e4b0aa commit 86b8053

File tree

15 files changed

+336
-330
lines changed

15 files changed

+336
-330
lines changed

server/src/main/java/org/elasticsearch/action/admin/indices/mapping/put/TransportPutMappingAction.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ static String checkForSystemIndexViolations(SystemIndices systemIndices, Index[]
159159

160160
for (Index index : concreteIndices) {
161161
final SystemIndexDescriptor descriptor = systemIndices.findMatchingDescriptor(index.getName());
162-
if (descriptor != null && descriptor.isAutomaticallyManaged()) {
162+
if (descriptor != null && descriptor.isAutomaticallyManaged() && descriptor.hasDynamicMappings() == false) {
163163
final String descriptorMappings = descriptor.getMappings();
164164
// Technically we could trip over a difference in whitespace here, but then again nobody should be trying to manually
165165
// update a descriptor's mappings.

server/src/main/java/org/elasticsearch/indices/SystemIndexDescriptor.java

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,15 @@
1818
import org.elasticsearch.common.Strings;
1919
import org.elasticsearch.common.settings.Settings;
2020
import org.elasticsearch.common.xcontent.XContentBuilder;
21+
import org.elasticsearch.common.xcontent.XContentHelper;
22+
import org.elasticsearch.common.xcontent.json.JsonXContent;
2123
import org.elasticsearch.index.mapper.MapperService;
2224

2325
import java.util.ArrayList;
2426
import java.util.Collections;
2527
import java.util.List;
2628
import java.util.Locale;
29+
import java.util.Map;
2730
import java.util.Objects;
2831

2932
/**
@@ -75,6 +78,9 @@ public class SystemIndexDescriptor {
7578
/** The minimum cluster node version required for this descriptor, or null if there is no restriction */
7679
private final Version minimumNodeVersion;
7780

81+
/** Whether there are dynamic fields in this descriptor's mappings */
82+
private final boolean hasDynamicMappings;
83+
7884
/**
7985
* Creates a descriptor for system indices matching the supplied pattern. These indices will not be managed
8086
* by Elasticsearch internally.
@@ -176,8 +182,12 @@ public SystemIndexDescriptor(String indexPattern, String description) {
176182
this.origin = origin;
177183
this.indexType = indexType;
178184
this.minimumNodeVersion = minimumNodeVersion;
185+
186+
this.hasDynamicMappings = this.mappings != null
187+
&& findDynamicMapping(XContentHelper.convertToMap(JsonXContent.jsonXContent, mappings, false));
179188
}
180189

190+
181191
/**
182192
* @return The pattern of index names that this descriptor will be used for.
183193
*/
@@ -266,6 +276,10 @@ public String getIndexType() {
266276
return indexType;
267277
}
268278

279+
public boolean hasDynamicMappings() {
280+
return this.hasDynamicMappings;
281+
}
282+
269283
/**
270284
* Checks that this descriptor can be used within this cluster e.g. the cluster supports all required
271285
* features, by comparing the supplied minimum node version to this descriptor's minimum version.
@@ -436,4 +450,32 @@ private static String patternToRegex(String input) {
436450
output = output.replaceAll("\\*", ".*");
437451
return output;
438452
}
453+
454+
/**
455+
* Recursively searches for <code>dynamic: true</code> in the supplies mappings
456+
* @param map a parsed fragment of an index's mappings
457+
* @return whether the fragment contains a dynamic mapping
458+
*/
459+
@SuppressWarnings("unchecked")
460+
static boolean findDynamicMapping(Map<String, Object> map) {
461+
if (map == null) {
462+
return false;
463+
}
464+
465+
for (Map.Entry<String, Object> entry : map.entrySet()) {
466+
final String key = entry.getKey();
467+
final Object value = entry.getValue();
468+
if (key.equals("dynamic") && (value instanceof Boolean) && ((Boolean) value)) {
469+
return true;
470+
}
471+
472+
if (value instanceof Map) {
473+
if (findDynamicMapping((Map<String, Object>) value)) {
474+
return true;
475+
}
476+
}
477+
}
478+
479+
return false;
480+
}
439481
}

server/src/main/java/org/elasticsearch/indices/SystemIndexManager.java

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,6 @@
88

99
package org.elasticsearch.indices;
1010

11-
import static org.elasticsearch.cluster.metadata.IndexMetadata.INDEX_FORMAT_SETTING;
12-
13-
import java.util.List;
14-
import java.util.Map;
15-
import java.util.concurrent.atomic.AtomicBoolean;
16-
import java.util.stream.Collectors;
17-
1811
import org.apache.logging.log4j.LogManager;
1912
import org.apache.logging.log4j.Logger;
2013
import org.apache.logging.log4j.message.ParameterizedMessage;
@@ -39,6 +32,13 @@
3932
import org.elasticsearch.common.xcontent.XContentType;
4033
import org.elasticsearch.gateway.GatewayService;
4134

35+
import java.util.List;
36+
import java.util.Map;
37+
import java.util.concurrent.atomic.AtomicBoolean;
38+
import java.util.stream.Collectors;
39+
40+
import static org.elasticsearch.cluster.metadata.IndexMetadata.INDEX_FORMAT_SETTING;
41+
4242
/**
4343
* This class ensures that all system indices have up-to-date mappings, provided
4444
* those indices can be automatically managed. Only some system indices are managed
@@ -52,6 +52,11 @@ public class SystemIndexManager implements ClusterStateListener {
5252
private final Client client;
5353
private final AtomicBoolean isUpgradeInProgress;
5454

55+
/**
56+
* Creates a new manager
57+
* @param systemIndices the indices to manage
58+
* @param client used to update the cluster
59+
*/
5560
public SystemIndexManager(SystemIndices systemIndices, Client client) {
5661
this.systemIndices = systemIndices;
5762
this.client = client;
@@ -138,6 +143,11 @@ UpgradeStatus getUpgradeStatus(ClusterState clusterState, SystemIndexDescriptor
138143
// The messages below will be logged on every cluster state update, which is why even in the index closed / red
139144
// cases, the log levels are DEBUG.
140145

146+
if (indexState == null) {
147+
logger.debug("Index {} does not exist yet", indexDescription);
148+
return UpgradeStatus.UP_TO_DATE;
149+
}
150+
141151
if (indexState.indexState == IndexMetadata.State.CLOSE) {
142152
logger.debug("Index {} is closed. This is likely to prevent some features from functioning correctly", indexDescription);
143153
return UpgradeStatus.CLOSED;
@@ -199,10 +209,16 @@ public void onFailure(Exception e) {
199209

200210
/**
201211
* Derives a summary of the current state of a system index, relative to the given cluster state.
212+
* @param state the cluster state from which to derive the index state
213+
* @param descriptor the system index to check
214+
* @return a summary of the index state, or <code>null</code> if the index doesn't exist
202215
*/
203216
State calculateIndexState(ClusterState state, SystemIndexDescriptor descriptor) {
204217
final IndexMetadata indexMetadata = state.metadata().index(descriptor.getPrimaryIndex());
205-
assert indexMetadata != null;
218+
219+
if (indexMetadata == null) {
220+
return null;
221+
}
206222

207223
final boolean isIndexUpToDate = INDEX_FORMAT_SETTING.get(indexMetadata.getSettings()) == descriptor.getIndexFormat();
208224

@@ -256,6 +272,8 @@ private Version readMappingVersion(SystemIndexDescriptor descriptor, MappingMeta
256272
final String versionString = (String) meta.get(descriptor.getVersionMetaKey());
257273
if (versionString == null) {
258274
logger.warn("No value found in mappings for [_meta.{}]", descriptor.getVersionMetaKey());
275+
// If we called `Version.fromString(null)`, it would return `Version.CURRENT` and we wouldn't update the mappings
276+
return Version.V_EMPTY;
259277
}
260278
return Version.fromString(versionString);
261279
} catch (ElasticsearchParseException e) {

server/src/test/java/org/elasticsearch/indices/SystemIndexDescriptorTests.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,21 @@
88

99
package org.elasticsearch.indices;
1010

11+
import org.elasticsearch.common.xcontent.XContentHelper;
12+
import org.elasticsearch.common.xcontent.json.JsonXContent;
1113
import org.elasticsearch.test.ESTestCase;
1214

15+
import java.util.Map;
16+
17+
import static org.elasticsearch.indices.SystemIndexDescriptor.findDynamicMapping;
1318
import static org.hamcrest.Matchers.containsString;
1419
import static org.hamcrest.Matchers.equalTo;
1520

1621
public class SystemIndexDescriptorTests extends ESTestCase {
1722

23+
/**
24+
* Tests the various validation rules that are applied when creating a new system index descriptor.
25+
*/
1826
public void testValidation() {
1927
{
2028
Exception ex = expectThrows(NullPointerException.class,
@@ -75,4 +83,35 @@ public void testValidation() {
7583
);
7684
}
7785
}
86+
87+
/**
88+
* Check that a system index descriptor correctly identifies the presence of a dynamic mapping when once is present.
89+
*/
90+
public void testFindDynamicMappingsWithDynamicMapping() {
91+
String json = "{"
92+
+ " \"foo\": {"
93+
+ " \"bar\": {"
94+
+ " \"dynamic\": false"
95+
+ " },"
96+
+ " \"baz\": {"
97+
+ " \"dynamic\": true"
98+
+ " }"
99+
+ " }"
100+
+ "}";
101+
102+
final Map<String, Object> mappings = XContentHelper.convertToMap(JsonXContent.jsonXContent, json, false);
103+
104+
assertThat(findDynamicMapping(mappings), equalTo(true));
105+
}
106+
107+
/**
108+
* Check that a system index descriptor correctly identifies the absence of a dynamic mapping when none are present.
109+
*/
110+
public void testFindDynamicMappingsWithoutDynamicMapping() {
111+
String json = "{ \"foo\": { \"bar\": { \"dynamic\": false } } }";
112+
113+
final Map<String, Object> mappings = XContentHelper.convertToMap(JsonXContent.jsonXContent, json, false);
114+
115+
assertThat(findDynamicMapping(mappings), equalTo(false));
116+
}
78117
}

server/src/test/java/org/elasticsearch/indices/SystemIndexManagerTests.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,19 @@ public void testManagerProcessesIndicesWithOutdatedMappings() {
222222
assertThat(manager.getUpgradeStatus(clusterStateBuilder.build(), DESCRIPTOR), equalTo(UpgradeStatus.NEEDS_MAPPINGS_UPDATE));
223223
}
224224

225+
/**
226+
* Check that the manager will try to upgrade indices where the version in the metadata is null or absent.
227+
*/
228+
public void testManagerProcessesIndicesWithNullVersionMetadata() {
229+
SystemIndices systemIndices = new SystemIndices(org.elasticsearch.common.collect.Map.of("MyIndex", FEATURE));
230+
SystemIndexManager manager = new SystemIndexManager(systemIndices, client);
231+
232+
final ClusterState.Builder clusterStateBuilder = createClusterState(Strings.toString(getMappings(null)));
233+
markShardsAvailable(clusterStateBuilder);
234+
235+
assertThat(manager.getUpgradeStatus(clusterStateBuilder.build(), DESCRIPTOR), equalTo(UpgradeStatus.NEEDS_MAPPINGS_UPDATE));
236+
}
237+
225238
/**
226239
* Check that the manager submits the expected request for an index whose mappings are out-of-date.
227240
*/

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/watcher/support/WatcherIndexTemplateRegistryField.java

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public final class WatcherIndexTemplateRegistryField {
1919
// version 11: watch history indices are hidden
2020
// version 12: templates changed to composable templates
2121
// version 13: add `allow_auto_create` setting
22+
// version 14: remove watches and triggered watches, these are now system indices
2223
// Note: if you change this, also inform the kibana team around the watcher-ui
2324
public static final int INDEX_TEMPLATE_VERSION = 13;
2425
public static final int INDEX_TEMPLATE_VERSION_10 = 10;
@@ -32,17 +33,11 @@ public final class WatcherIndexTemplateRegistryField {
3233
public static final String HISTORY_TEMPLATE_NAME_NO_ILM_10 = ".watch-history-no-ilm-" + INDEX_TEMPLATE_VERSION_10;
3334
public static final String HISTORY_TEMPLATE_NAME_NO_ILM_11 = ".watch-history-no-ilm-" + INDEX_TEMPLATE_VERSION_11;
3435
public static final String HISTORY_TEMPLATE_NAME_NO_ILM_12 = ".watch-history-no-ilm-" + INDEX_TEMPLATE_VERSION_12;
35-
public static final String TRIGGERED_TEMPLATE_NAME = ".triggered_watches";
36-
public static final String TRIGGERED_TEMPLATE_NAME_11 = ".triggered_watches-11";
37-
public static final String TRIGGERED_TEMPLATE_NAME_12 = ".triggered_watches-12";
38-
public static final String WATCHES_TEMPLATE_NAME = ".watches";
39-
public static final String WATCHES_TEMPLATE_NAME_11 = ".watches-11";
40-
public static final String WATCHES_TEMPLATE_NAME_12 = ".watches-12";
4136
public static final String[] TEMPLATE_NAMES = new String[] {
42-
HISTORY_TEMPLATE_NAME, TRIGGERED_TEMPLATE_NAME, WATCHES_TEMPLATE_NAME
37+
HISTORY_TEMPLATE_NAME
4338
};
4439
public static final String[] TEMPLATE_NAMES_NO_ILM = new String[] {
45-
HISTORY_TEMPLATE_NAME_NO_ILM, TRIGGERED_TEMPLATE_NAME, WATCHES_TEMPLATE_NAME
40+
HISTORY_TEMPLATE_NAME_NO_ILM
4641
};
4742

4843
private WatcherIndexTemplateRegistryField() {}

x-pack/plugin/core/src/main/resources/triggered-watches.json

Lines changed: 0 additions & 46 deletions
This file was deleted.

x-pack/plugin/core/src/main/resources/watches.json

Lines changed: 0 additions & 68 deletions
This file was deleted.

0 commit comments

Comments
 (0)