Skip to content

Commit 4f0107c

Browse files
authored
Merge branch 'main' into refactor-ilm-tests
2 parents a69c909 + dc3ee44 commit 4f0107c

File tree

8 files changed

+227
-4
lines changed

8 files changed

+227
-4
lines changed

docs/changelog/131706.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 131706
2+
summary: Add extension points to remediate index metadata in during snapshot restore
3+
area: Snapshot/Restore
4+
type: enhancement
5+
issues: []

docs/reference/elasticsearch/rest-apis/reindex-indices.md

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ You can learn how to:
3535
- [Reindex with an ingest pipeline](#reindex-with-an-ingest-pipeline)
3636
- [Reindex from remote](#reindex-from-remote)
3737

38+
**Troubleshooting**
39+
- [Monitor reindex tasks](#monitor-reindex-tasks)
40+
- [Diagnose node failures](#diagnose-node-failures)
41+
- [Version conflicts](#version-conflicts)
42+
3843
## Basic reindexing example
3944

4045
Use the Reindex API to copy all documents from one index to another.
@@ -731,4 +736,50 @@ POST _reindex
731736
Reindex from remote supports configurable SSL settings.
732737
These must be specified in the `elasticsearch.yml` file, with the exception of the secure settings, which you add in the {{es}} keystore.
733738
It is not possible to configure SSL in the body of the reindex API request.
734-
Refer to [Reindex settings](/reference/elasticsearch/configuration-reference/index-management-settings.md#reindex-settings).
739+
Refer to [Reindex settings](/reference/elasticsearch/configuration-reference/index-management-settings.md#reindex-settings).
740+
741+
## Monitor reindex tasks [monitor-reindex-tasks]
742+
743+
When run asynchronously with `wait_for_completion=false`, a reindex task can be monitored with the task management API:
744+
```console
745+
GET _tasks/r1A2WoRbTwKZ516z6NEs5A:36619
746+
```
747+
% TEST[catch:missing]
748+
749+
To view all currently running reindex tasks:
750+
```console
751+
GET _tasks?actions=*reindex
752+
```
753+
754+
You can also cancel a running reindex task:
755+
```console
756+
POST _tasks/r1A2WoRbTwKZ516z6NEs5A:36619/_cancel
757+
```
758+
759+
## Diagnose node failures [diagnose-node-failures]
760+
761+
Node crashes can sometimes be caused by insufficient disk space. To check disk allocation across your cluster:
762+
```console
763+
GET _cat/allocation?v
764+
```
765+
766+
## Version conflicts [version-conflicts]
767+
768+
By default, version conflicts abort the reindexing process.
769+
To continue reindexing in the case of conflicts, set `conflicts` to `proceed`.
770+
This may be necessary when retrying a failed reindex operation, as the destination index could be left in a partial state.
771+
772+
```console
773+
POST _reindex
774+
{
775+
"source": {
776+
"index": "my-index-000001"
777+
},
778+
"dest": {
779+
"index": "my-new-index-000001",
780+
"op_type": "create"
781+
},
782+
"conflicts": "proceed"
783+
}
784+
```
785+
% TEST[setup:my_index]
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
package org.elasticsearch.snapshots;
11+
12+
import org.elasticsearch.action.admin.cluster.snapshots.restore.RestoreSnapshotResponse;
13+
import org.elasticsearch.action.admin.indices.get.GetIndexResponse;
14+
import org.elasticsearch.client.internal.Client;
15+
import org.elasticsearch.cluster.metadata.IndexMetadata;
16+
import org.elasticsearch.common.settings.Settings;
17+
import org.elasticsearch.core.TimeValue;
18+
import org.elasticsearch.plugins.Plugin;
19+
20+
import java.util.Arrays;
21+
import java.util.Collection;
22+
import java.util.stream.Stream;
23+
24+
import static org.elasticsearch.index.IndexSettings.INDEX_SEARCH_IDLE_AFTER;
25+
import static org.hamcrest.Matchers.equalTo;
26+
import static org.hamcrest.Matchers.greaterThan;
27+
28+
public class RemediateSnapshotIT extends AbstractSnapshotIntegTestCase {
29+
30+
@Override
31+
protected Collection<Class<? extends Plugin>> nodePlugins() {
32+
return Stream.concat(Stream.of(RemediateSnapshotTestPlugin.class), super.nodePlugins().stream()).toList();
33+
}
34+
35+
public void testRemediationOnRestore() {
36+
Client client = client();
37+
38+
createRepository("test-repo", "fs");
39+
40+
createIndex("test-idx-1", "test-idx-2", "test-idx-3");
41+
ensureGreen();
42+
43+
GetIndexResponse getIndexResponse = client.admin()
44+
.indices()
45+
.prepareGetIndex(TEST_REQUEST_TIMEOUT)
46+
.setIndices("test-idx-1", "test-idx-2", "test-idx-3")
47+
.get();
48+
49+
assertThat(
50+
INDEX_SEARCH_IDLE_AFTER.get(getIndexResponse.settings().get("test-idx-1")),
51+
equalTo(INDEX_SEARCH_IDLE_AFTER.getDefault(Settings.EMPTY))
52+
);
53+
assertThat(
54+
INDEX_SEARCH_IDLE_AFTER.get(getIndexResponse.settings().get("test-idx-2")),
55+
equalTo(INDEX_SEARCH_IDLE_AFTER.getDefault(Settings.EMPTY))
56+
);
57+
assertThat(
58+
INDEX_SEARCH_IDLE_AFTER.get(getIndexResponse.settings().get("test-idx-3")),
59+
equalTo(INDEX_SEARCH_IDLE_AFTER.getDefault(Settings.EMPTY))
60+
);
61+
62+
indexRandomDocs("test-idx-1", 100);
63+
indexRandomDocs("test-idx-2", 100);
64+
65+
createSnapshot("test-repo", "test-snap", Arrays.asList("test-idx-1", "test-idx-2"));
66+
67+
logger.info("--> close snapshot indices");
68+
client.admin().indices().prepareClose("test-idx-1", "test-idx-2").get();
69+
70+
logger.info("--> restore indices");
71+
RestoreSnapshotResponse restoreSnapshotResponse = client.admin()
72+
.cluster()
73+
.prepareRestoreSnapshot(TEST_REQUEST_TIMEOUT, "test-repo", "test-snap")
74+
.setWaitForCompletion(true)
75+
.get();
76+
assertThat(restoreSnapshotResponse.getRestoreInfo().totalShards(), greaterThan(0));
77+
78+
GetIndexResponse getIndexResponseAfter = client.admin()
79+
.indices()
80+
.prepareGetIndex(TEST_REQUEST_TIMEOUT)
81+
.setIndices("test-idx-1", "test-idx-2", "test-idx-3")
82+
.get();
83+
84+
assertDocCount("test-idx-1", 100L);
85+
assertThat(INDEX_SEARCH_IDLE_AFTER.get(getIndexResponseAfter.settings().get("test-idx-1")), equalTo(TimeValue.timeValueMinutes(2)));
86+
assertDocCount("test-idx-2", 100L);
87+
assertThat(INDEX_SEARCH_IDLE_AFTER.get(getIndexResponseAfter.settings().get("test-idx-2")), equalTo(TimeValue.timeValueMinutes(2)));
88+
}
89+
90+
/**
91+
* Dummy plugin to load SPI function off of
92+
*/
93+
public static class RemediateSnapshotTestPlugin extends Plugin {}
94+
95+
public static class RemediateSnapshotTestTransformer implements IndexMetadataRestoreTransformer {
96+
@Override
97+
public IndexMetadata updateIndexMetadata(IndexMetadata original) {
98+
// Set a property to something mild, outside its default
99+
return IndexMetadata.builder(original)
100+
.settings(
101+
Settings.builder()
102+
.put(original.getSettings())
103+
.put(INDEX_SEARCH_IDLE_AFTER.getKey(), TimeValue.timeValueMinutes(2))
104+
.build()
105+
)
106+
.build();
107+
}
108+
}
109+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#
2+
# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
# or more contributor license agreements. Licensed under the "Elastic License
4+
# 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
# Public License v 1"; you may not use this file except in compliance with, at
6+
# your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
# License v3.0 only", or the "Server Side Public License, v 1".
8+
#
9+
10+
org.elasticsearch.snapshots.RemediateSnapshotIT$RemediateSnapshotTestTransformer

server/src/main/java/org/elasticsearch/node/NodeConstruction.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,8 @@
205205
import org.elasticsearch.search.SearchUtils;
206206
import org.elasticsearch.search.aggregations.support.AggregationUsageService;
207207
import org.elasticsearch.shutdown.PluginShutdownService;
208+
import org.elasticsearch.snapshots.IndexMetadataRestoreTransformer;
209+
import org.elasticsearch.snapshots.IndexMetadataRestoreTransformer.NoOpRestoreTransformer;
208210
import org.elasticsearch.snapshots.InternalSnapshotsInfoService;
209211
import org.elasticsearch.snapshots.RepositoryIntegrityHealthIndicatorService;
210212
import org.elasticsearch.snapshots.RestoreService;
@@ -1160,7 +1162,8 @@ public Map<String, String> queryFields() {
11601162
indicesService,
11611163
fileSettingsService,
11621164
threadPool,
1163-
projectResolver.supportsMultipleProjects()
1165+
projectResolver.supportsMultipleProjects(),
1166+
pluginsService.loadSingletonServiceProvider(IndexMetadataRestoreTransformer.class, NoOpRestoreTransformer::getInstance)
11641167
);
11651168

11661169
DiscoveryModule discoveryModule = createDiscoveryModule(
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
package org.elasticsearch.snapshots;
11+
12+
import org.elasticsearch.cluster.metadata.IndexMetadata;
13+
14+
/**
15+
* A temporary interface meant to allow a plugin to provide remedial logic for index metadata being restored from a snapshot. This was
16+
* added to address an allocation issue and will eventually be removed once any affected snapshots age out.
17+
*/
18+
public interface IndexMetadataRestoreTransformer {
19+
IndexMetadata updateIndexMetadata(IndexMetadata original);
20+
21+
/**
22+
* A default implementation of {@link IndexMetadataRestoreTransformer} which does nothing
23+
*/
24+
final class NoOpRestoreTransformer implements IndexMetadataRestoreTransformer {
25+
public static final NoOpRestoreTransformer INSTANCE = new NoOpRestoreTransformer();
26+
27+
public static NoOpRestoreTransformer getInstance() {
28+
return INSTANCE;
29+
}
30+
31+
private NoOpRestoreTransformer() {}
32+
33+
@Override
34+
public IndexMetadata updateIndexMetadata(IndexMetadata original) {
35+
return original;
36+
}
37+
}
38+
}

server/src/main/java/org/elasticsearch/snapshots/RestoreService.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,8 @@ public final class RestoreService implements ClusterStateApplier {
206206

207207
private final Executor snapshotMetaExecutor;
208208

209+
private final IndexMetadataRestoreTransformer indexMetadataRestoreTransformer;
210+
209211
private volatile boolean refreshRepositoryUuidOnRestore;
210212

211213
public RestoreService(
@@ -219,7 +221,8 @@ public RestoreService(
219221
IndicesService indicesService,
220222
FileSettingsService fileSettingsService,
221223
ThreadPool threadPool,
222-
boolean deserializeProjectMetadata
224+
boolean deserializeProjectMetadata,
225+
IndexMetadataRestoreTransformer indexMetadataRestoreTransformer
223226
) {
224227
this.clusterService = clusterService;
225228
this.repositoriesService = repositoriesService;
@@ -240,6 +243,7 @@ public RestoreService(
240243
this.refreshRepositoryUuidOnRestore = REFRESH_REPO_UUID_ON_RESTORE_SETTING.get(clusterService.getSettings());
241244
clusterService.getClusterSettings()
242245
.addSettingsUpdateConsumer(REFRESH_REPO_UUID_ON_RESTORE_SETTING, this::setRefreshRepositoryUuidOnRestore);
246+
this.indexMetadataRestoreTransformer = indexMetadataRestoreTransformer;
243247
}
244248

245249
/**
@@ -521,6 +525,8 @@ private void startRestore(
521525
}
522526
for (IndexId indexId : repositoryData.resolveIndices(requestedIndicesIncludingSystem).values()) {
523527
IndexMetadata snapshotIndexMetaData = repository.getSnapshotIndexMetaData(repositoryData, snapshotId, indexId);
528+
// Update the snapshot index metadata before adding it to the metadata
529+
snapshotIndexMetaData = indexMetadataRestoreTransformer.updateIndexMetadata(snapshotIndexMetaData);
524530
if (snapshotIndexMetaData.isSystem()) {
525531
if (requestIndices.contains(indexId.getName())) {
526532
explicitlyRequestedSystemIndices.add(indexId.getName());

server/src/test/java/org/elasticsearch/snapshots/SnapshotResiliencyTests.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2703,7 +2703,8 @@ public boolean clusterHasFeature(ClusterState state, NodeFeature feature) {
27032703
indicesService,
27042704
mock(FileSettingsService.class),
27052705
threadPool,
2706-
false
2706+
false,
2707+
IndexMetadataRestoreTransformer.NoOpRestoreTransformer.getInstance()
27072708
);
27082709
actions.put(
27092710
TransportPutMappingAction.TYPE,

0 commit comments

Comments
 (0)