Skip to content

Commit 0c9b9c1

Browse files
authored
Migrate .tasks to be auto-managed (#65959)
Part of #61656. Change the `.tasks` system index descriptor so that the index can be automatically managed by Elasticsearch e.g. created on-demand, mapping kept up-to-date, etc. Also add an integration test to exercise the `SystemIndexManager` end-to-end.
1 parent bf96bca commit 0c9b9c1

File tree

6 files changed

+353
-155
lines changed

6 files changed

+353
-155
lines changed
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.indices;
21+
22+
import org.elasticsearch.Version;
23+
import org.elasticsearch.action.ActionListener;
24+
import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsRequest;
25+
import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsResponse;
26+
import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateRequest;
27+
import org.elasticsearch.cluster.metadata.IndexMetadata;
28+
import org.elasticsearch.cluster.metadata.MappingMetadata;
29+
import org.elasticsearch.common.Strings;
30+
import org.elasticsearch.common.collect.ImmutableOpenMap;
31+
import org.elasticsearch.common.settings.Settings;
32+
import org.elasticsearch.common.util.CollectionUtils;
33+
import org.elasticsearch.common.xcontent.XContentBuilder;
34+
import org.elasticsearch.common.xcontent.XContentType;
35+
import org.elasticsearch.plugins.Plugin;
36+
import org.elasticsearch.plugins.SystemIndexPlugin;
37+
import org.elasticsearch.test.ESIntegTestCase;
38+
import org.junit.Before;
39+
40+
import java.io.IOException;
41+
import java.io.UncheckedIOException;
42+
import java.util.Collection;
43+
import java.util.List;
44+
import java.util.Locale;
45+
import java.util.Map;
46+
import java.util.concurrent.atomic.AtomicBoolean;
47+
48+
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
49+
import static org.elasticsearch.test.XContentTestUtils.convertToXContent;
50+
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
51+
import static org.hamcrest.Matchers.equalTo;
52+
53+
@ESIntegTestCase.ClusterScope(scope = ESIntegTestCase.Scope.TEST, numDataNodes = 0)
54+
public class SystemIndexManagerIT extends ESIntegTestCase {
55+
56+
private static final String INDEX_NAME = ".test-index";
57+
private static final String PRIMARY_INDEX_NAME = INDEX_NAME + "-1";
58+
59+
@Before
60+
public void beforeEach() {
61+
TestSystemIndexDescriptor.useNewMappings.set(false);
62+
}
63+
64+
@Override
65+
protected Collection<Class<? extends Plugin>> nodePlugins() {
66+
return CollectionUtils.appendToCopy(super.nodePlugins(), TestPlugin.class);
67+
}
68+
69+
/**
70+
* Check that if the the SystemIndexManager finds a managed index with out-of-date mappings, then
71+
* the manager updates those mappings.
72+
*/
73+
public void testSystemIndexManagerUpgradesMappings() throws Exception {
74+
internalCluster().startNodes(1);
75+
76+
// Trigger the creation of the system index
77+
assertAcked(prepareCreate(INDEX_NAME));
78+
ensureGreen(INDEX_NAME);
79+
80+
assertMappings(TestSystemIndexDescriptor.getOldMappings());
81+
82+
// Poke the test descriptor so that the mappings are now "updated"
83+
TestSystemIndexDescriptor.useNewMappings.set(true);
84+
85+
// Cause a cluster state update, so that the SystemIndexManager will update the mappings in our index
86+
triggerClusterStateUpdates();
87+
88+
assertBusy(() -> assertMappings(TestSystemIndexDescriptor.getNewMappings()));
89+
}
90+
91+
/**
92+
* Check that if the the SystemIndexManager finds a managed index with mappings that claim to be newer than
93+
* what it expects, then those mappings are left alone.
94+
*/
95+
public void testSystemIndexManagerLeavesNewerMappingsAlone() throws Exception {
96+
TestSystemIndexDescriptor.useNewMappings.set(true);
97+
98+
internalCluster().startNodes(1);
99+
// Trigger the creation of the system index
100+
assertAcked(prepareCreate(INDEX_NAME));
101+
ensureGreen(INDEX_NAME);
102+
103+
assertMappings(TestSystemIndexDescriptor.getNewMappings());
104+
105+
// Poke the test descriptor so that the mappings are now out-dated.
106+
TestSystemIndexDescriptor.useNewMappings.set(false);
107+
108+
// Cause a cluster state update, so that the SystemIndexManager's listener will execute
109+
triggerClusterStateUpdates();
110+
111+
// Mappings should be unchanged.
112+
assertBusy(() -> assertMappings(TestSystemIndexDescriptor.getNewMappings()));
113+
}
114+
115+
/**
116+
* Performs a cluster state update in order to trigger any cluster state listeners - specifically, SystemIndexManager.
117+
*/
118+
private void triggerClusterStateUpdates() {
119+
final String name = randomAlphaOfLength(5).toLowerCase(Locale.ROOT);
120+
client().admin().indices().putTemplate(new PutIndexTemplateRequest(name).patterns(List.of(name))).actionGet();
121+
}
122+
123+
/**
124+
* Fetch the mappings for {@link #INDEX_NAME} and verify that they match the expected mappings. Note that this is just
125+
* a dumb string comparison, so order of keys matters.
126+
*/
127+
private void assertMappings(String expectedMappings) {
128+
client().admin().indices().getMappings(new GetMappingsRequest().indices(INDEX_NAME), new ActionListener<>() {
129+
@Override
130+
public void onResponse(GetMappingsResponse getMappingsResponse) {
131+
final ImmutableOpenMap<String, MappingMetadata> mappings = getMappingsResponse.getMappings();
132+
assertThat(
133+
"Expected mappings to contain a key for [" + PRIMARY_INDEX_NAME + "], but found: " + mappings.toString(),
134+
mappings.containsKey(PRIMARY_INDEX_NAME),
135+
equalTo(true)
136+
);
137+
final Map<String, Object> sourceAsMap = mappings.get(PRIMARY_INDEX_NAME).getSourceAsMap();
138+
139+
try {
140+
assertThat(convertToXContent(sourceAsMap, XContentType.JSON).utf8ToString(), equalTo(expectedMappings));
141+
} catch (IOException e) {
142+
throw new UncheckedIOException(e);
143+
}
144+
}
145+
146+
@Override
147+
public void onFailure(Exception e) {
148+
throw new AssertionError("Couldn't fetch mappings for " + INDEX_NAME, e);
149+
}
150+
});
151+
}
152+
153+
/** A special kind of {@link SystemIndexDescriptor} that can toggle what kind of mappings it
154+
* expects. A real descriptor is immutable. */
155+
public static class TestSystemIndexDescriptor extends SystemIndexDescriptor {
156+
157+
public static final AtomicBoolean useNewMappings = new AtomicBoolean(false);
158+
private static final Settings settings = Settings.builder()
159+
.put(IndexMetadata.INDEX_NUMBER_OF_SHARDS_SETTING.getKey(), 1)
160+
.put(IndexMetadata.INDEX_AUTO_EXPAND_REPLICAS_SETTING.getKey(), "0-1")
161+
.put(IndexMetadata.SETTING_PRIORITY, Integer.MAX_VALUE)
162+
.build();
163+
164+
TestSystemIndexDescriptor() {
165+
super(INDEX_NAME + "*", PRIMARY_INDEX_NAME, "Test system index", null, settings, INDEX_NAME, 0, "version", "stack");
166+
}
167+
168+
@Override
169+
public boolean isAutomaticallyManaged() {
170+
return true;
171+
}
172+
173+
@Override
174+
public String getMappings() {
175+
return useNewMappings.get() ? getNewMappings() : getOldMappings();
176+
}
177+
178+
public static String getOldMappings() {
179+
try {
180+
final XContentBuilder builder = jsonBuilder();
181+
182+
builder.startObject();
183+
{
184+
builder.startObject("_meta");
185+
builder.field("version", Version.CURRENT.previousMajor().toString());
186+
builder.endObject();
187+
188+
builder.startObject("properties");
189+
{
190+
builder.startObject("foo");
191+
builder.field("type", "text");
192+
builder.endObject();
193+
}
194+
builder.endObject();
195+
}
196+
197+
builder.endObject();
198+
return Strings.toString(builder);
199+
} catch (IOException e) {
200+
throw new UncheckedIOException("Failed to build .test-index-1 index mappings", e);
201+
}
202+
}
203+
204+
public static String getNewMappings() {
205+
try {
206+
final XContentBuilder builder = jsonBuilder();
207+
208+
builder.startObject();
209+
{
210+
builder.startObject("_meta");
211+
builder.field("version", Version.CURRENT.toString());
212+
builder.endObject();
213+
214+
builder.startObject("properties");
215+
{
216+
builder.startObject("bar");
217+
builder.field("type", "text");
218+
builder.endObject();
219+
builder.startObject("foo");
220+
builder.field("type", "text");
221+
builder.endObject();
222+
}
223+
builder.endObject();
224+
}
225+
226+
builder.endObject();
227+
return Strings.toString(builder);
228+
} catch (IOException e) {
229+
throw new UncheckedIOException("Failed to build .test-index-1 index mappings", e);
230+
}
231+
}
232+
}
233+
234+
/** Just a test plugin to allow the test descriptor to be installed in the cluster. */
235+
public static class TestPlugin extends Plugin implements SystemIndexPlugin {
236+
@Override
237+
public Collection<SystemIndexDescriptor> getSystemIndexDescriptors(Settings settings) {
238+
return List.of(new TestSystemIndexDescriptor());
239+
}
240+
}
241+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ public SystemIndexDescriptor(String indexPattern, String description) {
9797
* Elasticsearch version when the index was created.
9898
* @param origin the client origin to use when creating this index.
9999
*/
100-
private SystemIndexDescriptor(
100+
SystemIndexDescriptor(
101101
String indexPattern,
102102
String primaryIndex,
103103
String description,

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ enum UpgradeStatus {
137137
UpgradeStatus getUpgradeStatus(ClusterState clusterState, SystemIndexDescriptor descriptor) {
138138
final State indexState = calculateIndexState(clusterState, descriptor);
139139

140-
final String indexDescription = "Index [" + descriptor.getPrimaryIndex() + "] (alias [" + descriptor.getAliasName() + "])";
140+
final String indexDescription = "[" + descriptor.getPrimaryIndex() + "] (alias [" + descriptor.getAliasName() + "])";
141141

142142
// The messages below will be logged on every cluster state update, which is why even in the index closed / red
143143
// cases, the log levels are DEBUG.
@@ -230,14 +230,17 @@ State calculateIndexState(ClusterState state, SystemIndexDescriptor descriptor)
230230
return new State(indexState, indexHealth, isIndexUpToDate, isMappingIsUpToDate);
231231
}
232232

233-
/** Checks whether an index's mappings are up-to-date */
233+
/**
234+
* Checks whether an index's mappings are up-to-date. If an index is encountered that has
235+
* a version higher than Version.CURRENT, it is still considered up-to-date.
236+
*/
234237
private boolean checkIndexMappingUpToDate(SystemIndexDescriptor descriptor, IndexMetadata indexMetadata) {
235238
final MappingMetadata mappingMetadata = indexMetadata.mapping();
236239
if (mappingMetadata == null) {
237240
return false;
238241
}
239242

240-
return Version.CURRENT.equals(readMappingVersion(descriptor, mappingMetadata));
243+
return Version.CURRENT.onOrBefore(readMappingVersion(descriptor, mappingMetadata));
241244
}
242245

243246
/**

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
import java.util.stream.Collectors;
3939

4040
import static java.util.stream.Collectors.toUnmodifiableList;
41-
import static org.elasticsearch.tasks.TaskResultsService.TASK_INDEX;
41+
import static org.elasticsearch.tasks.TaskResultsService.TASKS_DESCRIPTOR;
4242

4343
/**
4444
* This class holds the {@link SystemIndexDescriptor} objects that represent system indices the
@@ -47,7 +47,7 @@
4747
*/
4848
public class SystemIndices {
4949
private static final Map<String, Collection<SystemIndexDescriptor>> SERVER_SYSTEM_INDEX_DESCRIPTORS = Map.of(
50-
TaskResultsService.class.getName(), List.of(new SystemIndexDescriptor(TASK_INDEX + "*", "Task Result Index"))
50+
TaskResultsService.class.getName(), List.of(TASKS_DESCRIPTOR)
5151
);
5252

5353
private final CharacterRunAutomaton runAutomaton;

0 commit comments

Comments
 (0)