Skip to content

Commit 6110316

Browse files
committed
Reject follow request if following setting not enabled on follower (#32448)
Today we do not check if the `following_index` setting of the follower is enabled or not when processing a follow-request. If that setting is disabled, the follower will use the default engine, not the following engine. This change checks and rejects such invalid follow requests. Relates #30086
1 parent 81cbbcb commit 6110316

File tree

3 files changed

+98
-49
lines changed

3 files changed

+98
-49
lines changed

x-pack/plugin/ccr/src/main/java/org/elasticsearch/xpack/ccr/action/FollowIndexAction.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -499,7 +499,10 @@ static void validate(Request request, IndexMetaData leaderIndex, IndexMetaData f
499499
if (leaderIndex.getState() != IndexMetaData.State.OPEN || followIndex.getState() != IndexMetaData.State.OPEN) {
500500
throw new IllegalArgumentException("leader and follow index must be open");
501501
}
502-
502+
if (CcrSettings.CCR_FOLLOWING_INDEX_SETTING.get(followIndex.getSettings()) == false) {
503+
throw new IllegalArgumentException("the following index [" + request.followerIndex + "] is not ready " +
504+
"to follow; the setting [" + CcrSettings.CCR_FOLLOWING_INDEX_SETTING.getKey() + "] must be enabled.");
505+
}
503506
// Make a copy, remove settings that are allowed to be different and then compare if the settings are equal.
504507
Settings leaderSettings = filter(leaderIndex.getSettings());
505508
Settings followerSettings = filter(followIndex.getSettings());

x-pack/plugin/ccr/src/test/java/org/elasticsearch/xpack/ccr/ShardChangesIT.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
import static java.util.Collections.singletonMap;
6060
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
6161
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
62+
import static org.hamcrest.Matchers.containsString;
6263
import static org.hamcrest.Matchers.equalTo;
6364
import static org.hamcrest.Matchers.is;
6465
import static org.hamcrest.Matchers.notNullValue;
@@ -426,6 +427,32 @@ public void testFollowNonExistentIndex() throws Exception {
426427
expectThrows(IllegalArgumentException.class, () -> client().execute(FollowIndexAction.INSTANCE, followRequest3).actionGet());
427428
}
428429

430+
public void testValidateFollowingIndexSettings() throws Exception {
431+
assertAcked(client().admin().indices().prepareCreate("test-leader")
432+
.setSettings(Settings.builder().put(IndexSettings.INDEX_SOFT_DELETES_SETTING.getKey(), true)));
433+
// TODO: indexing should be optional but the current mapping logic requires for now.
434+
client().prepareIndex("test-leader", "doc", "id").setSource("{\"f\": \"v\"}", XContentType.JSON).get();
435+
assertAcked(client().admin().indices().prepareCreate("test-follower").get());
436+
IllegalArgumentException followError = expectThrows(IllegalArgumentException.class, () -> client().execute(
437+
FollowIndexAction.INSTANCE, createFollowRequest("test-leader", "test-follower")).actionGet());
438+
assertThat(followError.getMessage(), equalTo("the following index [test-follower] is not ready to follow;" +
439+
" the setting [index.xpack.ccr.following_index] must be enabled."));
440+
// updating the `following_index` with an open index must not be allowed.
441+
IllegalArgumentException updateError = expectThrows(IllegalArgumentException.class, () -> {
442+
client().admin().indices().prepareUpdateSettings("test-follower")
443+
.setSettings(Settings.builder().put(CcrSettings.CCR_FOLLOWING_INDEX_SETTING.getKey(), true)).get();
444+
});
445+
assertThat(updateError.getMessage(), containsString("Can't update non dynamic settings " +
446+
"[[index.xpack.ccr.following_index]] for open indices [[test-follower/"));
447+
assertAcked(client().admin().indices().prepareClose("test-follower"));
448+
assertAcked(client().admin().indices().prepareUpdateSettings("test-follower")
449+
.setSettings(Settings.builder().put(CcrSettings.CCR_FOLLOWING_INDEX_SETTING.getKey(), true)));
450+
assertAcked(client().admin().indices().prepareOpen("test-follower"));
451+
assertAcked(client().execute(FollowIndexAction.INSTANCE,
452+
createFollowRequest("test-leader", "test-follower")).actionGet());
453+
unfollowIndex("test-follower");
454+
}
455+
429456
public void testFollowIndex_lowMaxTranslogBytes() throws Exception {
430457
final String leaderIndexSettings = getIndexSettings(1, singletonMap(IndexSettings.INDEX_SOFT_DELETES_SETTING.getKey(), "true"));
431458
assertAcked(client().admin().indices().prepareCreate("index1").setSource(leaderIndexSettings, XContentType.JSON));

x-pack/plugin/ccr/src/test/java/org/elasticsearch/xpack/ccr/action/FollowIndexActionTests.java

Lines changed: 67 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@
88
import org.elasticsearch.Version;
99
import org.elasticsearch.cluster.metadata.IndexMetaData;
1010
import org.elasticsearch.cluster.metadata.IndexMetaData.State;
11-
import org.elasticsearch.common.collect.Tuple;
1211
import org.elasticsearch.common.settings.Settings;
1312
import org.elasticsearch.index.IndexSettings;
1413
import org.elasticsearch.index.MapperTestUtils;
1514
import org.elasticsearch.index.mapper.MapperService;
1615
import org.elasticsearch.test.ESTestCase;
16+
import org.elasticsearch.xpack.ccr.CcrSettings;
1717
import org.elasticsearch.xpack.ccr.ShardChangesIT;
1818

1919
import java.io.IOException;
@@ -31,42 +31,44 @@ public void testValidation() throws IOException {
3131
}
3232
{
3333
// should fail, because follow index does not exist
34-
IndexMetaData leaderIMD = createIMD("index1", 5);
34+
IndexMetaData leaderIMD = createIMD("index1", 5, Settings.EMPTY);
3535
Exception e = expectThrows(IllegalArgumentException.class, () -> FollowIndexAction.validate(request, leaderIMD, null, null));
3636
assertThat(e.getMessage(), equalTo("follow index [index2] does not exist"));
3737
}
3838
{
3939
// should fail because leader index does not have soft deletes enabled
40-
IndexMetaData leaderIMD = createIMD("index1", 5);
41-
IndexMetaData followIMD = createIMD("index2", 5);
40+
IndexMetaData leaderIMD = createIMD("index1", 5, Settings.EMPTY);
41+
IndexMetaData followIMD = createIMD("index2", 5, Settings.EMPTY);
4242
Exception e = expectThrows(IllegalArgumentException.class,
4343
() -> FollowIndexAction.validate(request, leaderIMD, followIMD, null));
4444
assertThat(e.getMessage(), equalTo("leader index [index1] does not have soft deletes enabled"));
4545
}
4646
{
4747
// should fail because the number of primary shards between leader and follow index are not equal
48-
IndexMetaData leaderIMD = createIMD("index1", 5, new Tuple<>(IndexSettings.INDEX_SOFT_DELETES_SETTING.getKey(), "true"));
49-
IndexMetaData followIMD = createIMD("index2", 4);
48+
IndexMetaData leaderIMD = createIMD("index1", 5, Settings.builder()
49+
.put(IndexSettings.INDEX_SOFT_DELETES_SETTING.getKey(), "true").build());
50+
IndexMetaData followIMD = createIMD("index2", 4, Settings.EMPTY);
5051
Exception e = expectThrows(IllegalArgumentException.class,
5152
() -> FollowIndexAction.validate(request, leaderIMD, followIMD, null));
5253
assertThat(e.getMessage(),
5354
equalTo("leader index primary shards [5] does not match with the number of shards of the follow index [4]"));
5455
}
5556
{
5657
// should fail, because leader index is closed
57-
IndexMetaData leaderIMD = createIMD("index1", State.CLOSE, "{}", 5,
58-
new Tuple<>(IndexSettings.INDEX_SOFT_DELETES_SETTING.getKey(), "true"));
59-
IndexMetaData followIMD = createIMD("index2", State.OPEN, "{}", 5,
60-
new Tuple<>(IndexSettings.INDEX_SOFT_DELETES_SETTING.getKey(), "true"));
58+
IndexMetaData leaderIMD = createIMD("index1", State.CLOSE, "{}", 5, Settings.builder()
59+
.put(IndexSettings.INDEX_SOFT_DELETES_SETTING.getKey(), "true").build());
60+
IndexMetaData followIMD = createIMD("index2", State.OPEN, "{}", 5, Settings.builder()
61+
.put(IndexSettings.INDEX_SOFT_DELETES_SETTING.getKey(), "true").build());
6162
Exception e = expectThrows(IllegalArgumentException.class,
6263
() -> FollowIndexAction.validate(request, leaderIMD, followIMD, null));
6364
assertThat(e.getMessage(), equalTo("leader and follow index must be open"));
6465
}
6566
{
6667
// should fail, because leader has a field with the same name mapped as keyword and follower as text
6768
IndexMetaData leaderIMD = createIMD("index1", State.OPEN, "{\"properties\": {\"field\": {\"type\": \"keyword\"}}}", 5,
68-
new Tuple<>(IndexSettings.INDEX_SOFT_DELETES_SETTING.getKey(), "true"));
69-
IndexMetaData followIMD = createIMD("index2", State.OPEN, "{\"properties\": {\"field\": {\"type\": \"text\"}}}", 5);
69+
Settings.builder().put(IndexSettings.INDEX_SOFT_DELETES_SETTING.getKey(), "true").build());
70+
IndexMetaData followIMD = createIMD("index2", State.OPEN, "{\"properties\": {\"field\": {\"type\": \"text\"}}}", 5,
71+
Settings.builder().put(CcrSettings.CCR_FOLLOWING_INDEX_SETTING.getKey(), true).build());
7072
MapperService mapperService = MapperTestUtils.newMapperService(xContentRegistry(), createTempDir(), Settings.EMPTY, "index2");
7173
mapperService.updateMapping(followIMD);
7274
Exception e = expectThrows(IllegalArgumentException.class,
@@ -76,35 +78,54 @@ public void testValidation() throws IOException {
7678
{
7779
// should fail because of non whitelisted settings not the same between leader and follow index
7880
String mapping = "{\"properties\": {\"field\": {\"type\": \"text\", \"analyzer\": \"my_analyzer\"}}}";
79-
IndexMetaData leaderIMD = createIMD("index1", State.OPEN, mapping, 5,
80-
new Tuple<>(IndexSettings.INDEX_SOFT_DELETES_SETTING.getKey(), "true"),
81-
new Tuple<>("index.analysis.analyzer.my_analyzer.type", "custom"),
82-
new Tuple<>("index.analysis.analyzer.my_analyzer.tokenizer", "whitespace"));
83-
IndexMetaData followIMD = createIMD("index2", State.OPEN, mapping, 5,
84-
new Tuple<>("index.analysis.analyzer.my_analyzer.type", "custom"),
85-
new Tuple<>("index.analysis.analyzer.my_analyzer.tokenizer", "standard"));
81+
IndexMetaData leaderIMD = createIMD("index1", State.OPEN, mapping, 5, Settings.builder()
82+
.put(IndexSettings.INDEX_SOFT_DELETES_SETTING.getKey(), "true")
83+
.put("index.analysis.analyzer.my_analyzer.type", "custom")
84+
.put("index.analysis.analyzer.my_analyzer.tokenizer", "whitespace").build());
85+
IndexMetaData followIMD = createIMD("index2", State.OPEN, mapping, 5, Settings.builder()
86+
.put(CcrSettings.CCR_FOLLOWING_INDEX_SETTING.getKey(), true)
87+
.put("index.analysis.analyzer.my_analyzer.type", "custom")
88+
.put("index.analysis.analyzer.my_analyzer.tokenizer", "standard").build());
8689
Exception e = expectThrows(IllegalArgumentException.class,
8790
() -> FollowIndexAction.validate(request, leaderIMD, followIMD, null));
8891
assertThat(e.getMessage(), equalTo("the leader and follower index settings must be identical"));
8992
}
93+
{
94+
// should fail because the following index does not have the following_index settings
95+
IndexMetaData leaderIMD = createIMD("index1", 5,
96+
Settings.builder().put(IndexSettings.INDEX_SOFT_DELETES_SETTING.getKey(), "true").build());
97+
Settings followingIndexSettings = randomBoolean() ? Settings.EMPTY :
98+
Settings.builder().put(CcrSettings.CCR_FOLLOWING_INDEX_SETTING.getKey(), false).build();
99+
IndexMetaData followIMD = createIMD("index2", 5, followingIndexSettings);
100+
MapperService mapperService = MapperTestUtils.newMapperService(xContentRegistry(), createTempDir(),
101+
followingIndexSettings, "index2");
102+
mapperService.updateMapping(followIMD);
103+
IllegalArgumentException error = expectThrows(IllegalArgumentException.class,
104+
() -> FollowIndexAction.validate(request, leaderIMD, followIMD, mapperService));
105+
assertThat(error.getMessage(), equalTo("the following index [index2] is not ready to follow; " +
106+
"the setting [index.xpack.ccr.following_index] must be enabled."));
107+
}
90108
{
91109
// should succeed
92-
IndexMetaData leaderIMD = createIMD("index1", 5, new Tuple<>(IndexSettings.INDEX_SOFT_DELETES_SETTING.getKey(), "true"));
93-
IndexMetaData followIMD = createIMD("index2", 5);
110+
IndexMetaData leaderIMD = createIMD("index1", 5, Settings.builder()
111+
.put(IndexSettings.INDEX_SOFT_DELETES_SETTING.getKey(), "true").build());
112+
IndexMetaData followIMD = createIMD("index2", 5, Settings.builder()
113+
.put(CcrSettings.CCR_FOLLOWING_INDEX_SETTING.getKey(), true).build());
94114
MapperService mapperService = MapperTestUtils.newMapperService(xContentRegistry(), createTempDir(), Settings.EMPTY, "index2");
95115
mapperService.updateMapping(followIMD);
96116
FollowIndexAction.validate(request, leaderIMD, followIMD, mapperService);
97117
}
98118
{
99119
// should succeed, index settings are identical
100120
String mapping = "{\"properties\": {\"field\": {\"type\": \"text\", \"analyzer\": \"my_analyzer\"}}}";
101-
IndexMetaData leaderIMD = createIMD("index1", State.OPEN, mapping, 5,
102-
new Tuple<>(IndexSettings.INDEX_SOFT_DELETES_SETTING.getKey(), "true"),
103-
new Tuple<>("index.analysis.analyzer.my_analyzer.type", "custom"),
104-
new Tuple<>("index.analysis.analyzer.my_analyzer.tokenizer", "standard"));
105-
IndexMetaData followIMD = createIMD("index2", State.OPEN, mapping, 5,
106-
new Tuple<>("index.analysis.analyzer.my_analyzer.type", "custom"),
107-
new Tuple<>("index.analysis.analyzer.my_analyzer.tokenizer", "standard"));
121+
IndexMetaData leaderIMD = createIMD("index1", State.OPEN, mapping, 5, Settings.builder()
122+
.put(IndexSettings.INDEX_SOFT_DELETES_SETTING.getKey(), "true")
123+
.put("index.analysis.analyzer.my_analyzer.type", "custom")
124+
.put("index.analysis.analyzer.my_analyzer.tokenizer", "standard").build());
125+
IndexMetaData followIMD = createIMD("index2", State.OPEN, mapping, 5, Settings.builder()
126+
.put(CcrSettings.CCR_FOLLOWING_INDEX_SETTING.getKey(), true)
127+
.put("index.analysis.analyzer.my_analyzer.type", "custom")
128+
.put("index.analysis.analyzer.my_analyzer.tokenizer", "standard").build());
108129
MapperService mapperService = MapperTestUtils.newMapperService(xContentRegistry(), createTempDir(),
109130
followIMD.getSettings(), "index2");
110131
mapperService.updateMapping(followIMD);
@@ -113,37 +134,35 @@ public void testValidation() throws IOException {
113134
{
114135
// should succeed despite whitelisted settings being different
115136
String mapping = "{\"properties\": {\"field\": {\"type\": \"text\", \"analyzer\": \"my_analyzer\"}}}";
116-
IndexMetaData leaderIMD = createIMD("index1", State.OPEN, mapping, 5,
117-
new Tuple<>(IndexSettings.INDEX_SOFT_DELETES_SETTING.getKey(), "true"),
118-
new Tuple<>(IndexSettings.INDEX_REFRESH_INTERVAL_SETTING.getKey(), "1s"),
119-
new Tuple<>("index.analysis.analyzer.my_analyzer.type", "custom"),
120-
new Tuple<>("index.analysis.analyzer.my_analyzer.tokenizer", "standard"));
121-
IndexMetaData followIMD = createIMD("index2", State.OPEN, mapping, 5,
122-
new Tuple<>(IndexSettings.INDEX_REFRESH_INTERVAL_SETTING.getKey(), "10s"),
123-
new Tuple<>("index.analysis.analyzer.my_analyzer.type", "custom"),
124-
new Tuple<>("index.analysis.analyzer.my_analyzer.tokenizer", "standard"));
137+
IndexMetaData leaderIMD = createIMD("index1", State.OPEN, mapping, 5, Settings.builder()
138+
.put(IndexSettings.INDEX_SOFT_DELETES_SETTING.getKey(), "true")
139+
.put(IndexSettings.INDEX_REFRESH_INTERVAL_SETTING.getKey(), "1s")
140+
.put("index.analysis.analyzer.my_analyzer.type", "custom")
141+
.put("index.analysis.analyzer.my_analyzer.tokenizer", "standard").build());
142+
IndexMetaData followIMD = createIMD("index2", State.OPEN, mapping, 5, Settings.builder()
143+
.put(CcrSettings.CCR_FOLLOWING_INDEX_SETTING.getKey(), true)
144+
.put(IndexSettings.INDEX_REFRESH_INTERVAL_SETTING.getKey(), "10s")
145+
.put("index.analysis.analyzer.my_analyzer.type", "custom")
146+
.put("index.analysis.analyzer.my_analyzer.tokenizer", "standard").build());
125147
MapperService mapperService = MapperTestUtils.newMapperService(xContentRegistry(), createTempDir(),
126148
followIMD.getSettings(), "index2");
127149
mapperService.updateMapping(followIMD);
128150
FollowIndexAction.validate(request, leaderIMD, followIMD, mapperService);
129151
}
130152
}
131153

132-
private static IndexMetaData createIMD(String index, int numShards, Tuple<?, ?>... settings) throws IOException {
133-
return createIMD(index, State.OPEN, "{\"properties\": {}}", numShards, settings);
154+
private static IndexMetaData createIMD(String index, int numberOfShards, Settings settings) throws IOException {
155+
return createIMD(index, State.OPEN, "{\"properties\": {}}", numberOfShards, settings);
134156
}
135157

136-
private static IndexMetaData createIMD(String index, State state, String mapping, int numShards,
137-
Tuple<?, ?>... settings) throws IOException {
138-
Settings.Builder settingsBuilder = settings(Version.CURRENT);
139-
for (Tuple<?, ?> setting : settings) {
140-
settingsBuilder.put((String) setting.v1(), (String) setting.v2());
141-
}
142-
return IndexMetaData.builder(index).settings(settingsBuilder)
143-
.numberOfShards(numShards)
158+
private static IndexMetaData createIMD(String index, State state, String mapping, int numberOfShards,
159+
Settings settings) throws IOException {
160+
return IndexMetaData.builder(index)
161+
.settings(settings(Version.CURRENT).put(settings))
162+
.numberOfShards(numberOfShards)
144163
.state(state)
145164
.numberOfReplicas(0)
146-
.setRoutingNumShards(numShards)
165+
.setRoutingNumShards(numberOfShards)
147166
.putMapping("_doc", mapping)
148167
.build();
149168
}

0 commit comments

Comments
 (0)