Skip to content

Commit c77f8a2

Browse files
committed
[CCR] Add index setting to keep track of the index uuid of the leader index in the follow index.
The follow index api checks if the uuid in the follow index matches with uuid of the leader index and fails otherwise. This validation will prevent a follow index from following an incompatible leader index. The create_and_follow api will automatically add this index setting when it creates the follow index. Closes elastic#31505
1 parent 601ea76 commit c77f8a2

File tree

5 files changed

+116
-52
lines changed

5 files changed

+116
-52
lines changed

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ private CcrSettings() {
3232
public static final Setting<Boolean> CCR_FOLLOWING_INDEX_SETTING =
3333
Setting.boolSetting("index.xpack.ccr.following_index", false, Setting.Property.IndexScope);
3434

35+
/**
36+
* An internal setting used to keep track to what leader index a follow index associated. Do not use externally!
37+
*/
38+
public static final Setting<String> CCR_LEADER_INDEX_UUID_SETTING =
39+
Setting.simpleString("index.xpack.ccr.leader_index_uuid", Setting.Property.IndexScope);
40+
3541
/**
3642
* The settings defined by CCR.
3743
*
@@ -40,7 +46,8 @@ private CcrSettings() {
4046
static List<Setting<?>> getSettings() {
4147
return Arrays.asList(
4248
CCR_ENABLED_SETTING,
43-
CCR_FOLLOWING_INDEX_SETTING);
49+
CCR_FOLLOWING_INDEX_SETTING,
50+
CCR_LEADER_INDEX_UUID_SETTING);
4451
}
4552

4653
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@ public ClusterState execute(ClusterState currentState) throws Exception {
276276
settingsBuilder.put(IndexMetaData.SETTING_INDEX_UUID, UUIDs.randomBase64UUID());
277277
settingsBuilder.put(IndexMetaData.SETTING_INDEX_PROVIDED_NAME, followIndex);
278278
settingsBuilder.put(CcrSettings.CCR_FOLLOWING_INDEX_SETTING.getKey(), true);
279+
settingsBuilder.put(CcrSettings.CCR_LEADER_INDEX_UUID_SETTING.getKey(), leaderIndexMetaData.getIndexUUID());
279280
imdBuilder.settings(settingsBuilder);
280281

281282
// Copy mappings from leader IMD to follow IMD

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,16 @@ static void validate(Request request, IndexMetaData leaderIndex, IndexMetaData f
353353
if (followIndex == null) {
354354
throw new IllegalArgumentException("follow index [" + request.followIndex + "] does not exist");
355355
}
356+
if (followIndex.getSettings().getAsBoolean(CcrSettings.CCR_FOLLOWING_INDEX_SETTING.getKey(), false) == false) {
357+
throw new IllegalArgumentException("follow index [" + request.followIndex +
358+
"] does not have the follow index setting enabled");
359+
}
360+
String key = CcrSettings.CCR_LEADER_INDEX_UUID_SETTING.getKey();
361+
String leaderIndexUUID = leaderIndex.getIndex().getUUID();
362+
if (leaderIndexUUID.equals(followIndex.getSettings().get(key)) == false) {
363+
throw new IllegalArgumentException("follow index [" + request.followIndex + "] should reference [" + leaderIndexUUID +
364+
"] as leader index but instead reference [" + followIndex.getSettings().get(key) + "] as leader index");
365+
}
356366
if (leaderIndex.getSettings().getAsBoolean(IndexSettings.INDEX_SOFT_DELETES_SETTING.getKey(), false) == false) {
357367
throw new IllegalArgumentException("leader index [" + request.leaderIndex + "] does not have soft deletes enabled");
358368
}
@@ -384,6 +394,7 @@ private static Settings filter(Settings originalSettings) {
384394
Settings.Builder settings = Settings.builder().put(originalSettings);
385395
// Remove settings that are always going to be different between leader and follow index:
386396
settings.remove(CcrSettings.CCR_FOLLOWING_INDEX_SETTING.getKey());
397+
settings.remove(CcrSettings.CCR_LEADER_INDEX_UUID_SETTING.getKey());
387398
settings.remove(IndexMetaData.SETTING_INDEX_UUID);
388399
settings.remove(IndexMetaData.SETTING_INDEX_PROVIDED_NAME);
389400
settings.remove(IndexMetaData.SETTING_CREATION_DATE);

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

Lines changed: 54 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848

4949
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
5050
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
51+
import static org.hamcrest.Matchers.containsString;
5152
import static org.hamcrest.Matchers.equalTo;
5253
import static org.hamcrest.Matchers.notNullValue;
5354
import static org.hamcrest.Matchers.nullValue;
@@ -245,44 +246,21 @@ public void testSyncMappings() throws Exception {
245246
.get("index2").get("doc");
246247
assertThat(XContentMapValues.extractValue("properties.f.type", mappingMetaData.sourceAsMap()), equalTo("integer"));
247248
assertThat(XContentMapValues.extractValue("properties.k.type", mappingMetaData.sourceAsMap()), equalTo("long"));
248-
249-
final UnfollowIndexAction.Request unfollowRequest = new UnfollowIndexAction.Request();
250-
unfollowRequest.setFollowIndex("index2");
251-
client().execute(UnfollowIndexAction.INSTANCE, unfollowRequest).get();
252-
253-
assertBusy(() -> {
254-
final ClusterState clusterState = client().admin().cluster().prepareState().get().getState();
255-
final PersistentTasksCustomMetaData tasks = clusterState.getMetaData().custom(PersistentTasksCustomMetaData.TYPE);
256-
assertThat(tasks.tasks().size(), equalTo(0));
257-
258-
ListTasksRequest listTasksRequest = new ListTasksRequest();
259-
listTasksRequest.setDetailed(true);
260-
ListTasksResponse listTasksResponse = client().admin().cluster().listTasks(listTasksRequest).get();
261-
int numNodeTasks = 0;
262-
for (TaskInfo taskInfo : listTasksResponse.getTasks()) {
263-
if (taskInfo.getAction().startsWith(ListTasksAction.NAME) == false) {
264-
numNodeTasks++;
265-
}
266-
}
267-
assertThat(numNodeTasks, equalTo(0));
268-
});
249+
unfollowIndex("index2");
269250
}
270251

271252
public void testFollowIndexWithNestedField() throws Exception {
272253
final String leaderIndexSettings =
273254
getIndexSettingsWithNestedMapping(1, Collections.singletonMap(IndexSettings.INDEX_SOFT_DELETES_SETTING.getKey(), "true"));
274255
assertAcked(client().admin().indices().prepareCreate("index1").setSource(leaderIndexSettings, XContentType.JSON));
256+
ensureGreen("index1");
275257

276-
final String followerIndexSettings =
277-
getIndexSettingsWithNestedMapping(1, Collections.singletonMap(CcrSettings.CCR_FOLLOWING_INDEX_SETTING.getKey(), "true"));
278-
assertAcked(client().admin().indices().prepareCreate("index2").setSource(followerIndexSettings, XContentType.JSON));
279-
280-
ensureGreen("index1", "index2");
281-
282-
final FollowIndexAction.Request followRequest = new FollowIndexAction.Request();
258+
FollowIndexAction.Request followRequest = new FollowIndexAction.Request();
283259
followRequest.setLeaderIndex("index1");
284260
followRequest.setFollowIndex("index2");
285-
client().execute(FollowIndexAction.INSTANCE, followRequest).get();
261+
CreateAndFollowIndexAction.Request createAndFollowRequest = new CreateAndFollowIndexAction.Request();
262+
createAndFollowRequest.setFollowRequest(followRequest);
263+
client().execute(CreateAndFollowIndexAction.INSTANCE, createAndFollowRequest).get();
286264

287265
final int numDocs = randomIntBetween(2, 64);
288266
for (int i = 0; i < numDocs; i++) {
@@ -311,16 +289,7 @@ public void testFollowIndexWithNestedField() throws Exception {
311289
equalTo(Collections.singletonList(value)));
312290
});
313291
}
314-
315-
final UnfollowIndexAction.Request unfollowRequest = new UnfollowIndexAction.Request();
316-
unfollowRequest.setFollowIndex("index2");
317-
client().execute(UnfollowIndexAction.INSTANCE, unfollowRequest).get();
318-
319-
assertBusy(() -> {
320-
final ClusterState clusterState = client().admin().cluster().prepareState().get().getState();
321-
final PersistentTasksCustomMetaData tasks = clusterState.getMetaData().custom(PersistentTasksCustomMetaData.TYPE);
322-
assertThat(tasks.tasks().size(), equalTo(0));
323-
});
292+
unfollowIndex("index2");
324293
}
325294

326295
public void testUnfollowNonExistingIndex() {
@@ -347,6 +316,46 @@ public void testFollowNonExistentIndex() throws Exception {
347316
expectThrows(IllegalArgumentException.class, () -> client().execute(FollowIndexAction.INSTANCE, followRequest).actionGet());
348317
}
349318

319+
public void testDontFollowTheWrongIndex() throws Exception {
320+
String leaderIndexSettings = getIndexSettings(1,
321+
Collections.singletonMap(IndexSettings.INDEX_SOFT_DELETES_SETTING.getKey(), "true"));
322+
assertAcked(client().admin().indices().prepareCreate("index1").setSource(leaderIndexSettings, XContentType.JSON));
323+
ensureGreen("index1");
324+
assertAcked(client().admin().indices().prepareCreate("index3").setSource(leaderIndexSettings, XContentType.JSON));
325+
ensureGreen("index3");
326+
327+
FollowIndexAction.Request followRequest = new FollowIndexAction.Request();
328+
followRequest.setLeaderIndex("index1");
329+
followRequest.setFollowIndex("index2");
330+
331+
CreateAndFollowIndexAction.Request createAndFollowRequest = new CreateAndFollowIndexAction.Request();
332+
createAndFollowRequest.setFollowRequest(followRequest);
333+
client().execute(CreateAndFollowIndexAction.INSTANCE, createAndFollowRequest).get();
334+
335+
followRequest = new FollowIndexAction.Request();
336+
followRequest.setLeaderIndex("index3");
337+
followRequest.setFollowIndex("index4");
338+
339+
createAndFollowRequest = new CreateAndFollowIndexAction.Request();
340+
createAndFollowRequest.setFollowRequest(followRequest);
341+
client().execute(CreateAndFollowIndexAction.INSTANCE, createAndFollowRequest).get();
342+
343+
unfollowIndex("index2", "index4");
344+
345+
FollowIndexAction.Request wrongRequest1 = new FollowIndexAction.Request();
346+
wrongRequest1.setLeaderIndex("index1");
347+
wrongRequest1.setFollowIndex("index4");
348+
Exception e = expectThrows(IllegalArgumentException.class,
349+
() -> client().execute(FollowIndexAction.INSTANCE, wrongRequest1).actionGet());
350+
assertThat(e.getMessage(), containsString("follow index [index4] should reference"));
351+
352+
FollowIndexAction.Request wrongRequest2 = new FollowIndexAction.Request();
353+
wrongRequest2.setLeaderIndex("index3");
354+
wrongRequest2.setFollowIndex("index2");
355+
e = expectThrows(IllegalArgumentException.class, () -> client().execute(FollowIndexAction.INSTANCE, wrongRequest2).actionGet());
356+
assertThat(e.getMessage(), containsString("follow index [index2] should reference"));
357+
}
358+
350359
private CheckedRunnable<Exception> assertTask(final int numberOfPrimaryShards, final Map<ShardId, Long> numDocsPerShard) {
351360
return () -> {
352361
final ClusterState clusterState = client().admin().cluster().prepareState().get().getState();
@@ -383,10 +392,12 @@ private CheckedRunnable<Exception> assertTask(final int numberOfPrimaryShards, f
383392
};
384393
}
385394

386-
private void unfollowIndex(String index) throws Exception {
387-
final UnfollowIndexAction.Request unfollowRequest = new UnfollowIndexAction.Request();
388-
unfollowRequest.setFollowIndex(index);
389-
client().execute(UnfollowIndexAction.INSTANCE, unfollowRequest).get();
395+
private void unfollowIndex(String... indices) throws Exception {
396+
for (String index : indices) {
397+
final UnfollowIndexAction.Request unfollowRequest = new UnfollowIndexAction.Request();
398+
unfollowRequest.setFollowIndex(index);
399+
client().execute(UnfollowIndexAction.INSTANCE, unfollowRequest).get();
400+
}
390401
assertBusy(() -> {
391402
final ClusterState clusterState = client().admin().cluster().prepareState().get().getState();
392403
final PersistentTasksCustomMetaData tasks = clusterState.getMetaData().custom(PersistentTasksCustomMetaData.TYPE);

0 commit comments

Comments
 (0)